@blackcode_sa/metaestetics-api 1.4.18 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,
@@ -66,9 +71,15 @@ __export(index_exports, {
66
71
  PatientService: () => PatientService,
67
72
  PracticeType: () => PracticeType,
68
73
  PractitionerService: () => PractitionerService,
74
+ PractitionerStatus: () => PractitionerStatus,
75
+ PractitionerTokenStatus: () => PractitionerTokenStatus,
69
76
  PricingMeasure: () => PricingMeasure,
70
77
  ProcedureFamily: () => ProcedureFamily,
78
+ REGISTER_TOKENS_COLLECTION: () => REGISTER_TOKENS_COLLECTION,
79
+ SYNCED_CALENDARS_COLLECTION: () => SYNCED_CALENDARS_COLLECTION,
71
80
  SubscriptionModel: () => SubscriptionModel,
81
+ SyncedCalendarProvider: () => SyncedCalendarProvider,
82
+ SyncedCalendarsService: () => SyncedCalendarsService,
72
83
  TreatmentBenefit: () => TreatmentBenefit,
73
84
  USER_ERRORS: () => USER_ERRORS,
74
85
  UserService: () => UserService,
@@ -85,6 +96,8 @@ __export(index_exports, {
85
96
  appointmentReminderNotificationSchema: () => appointmentReminderNotificationSchema,
86
97
  baseNotificationSchema: () => baseNotificationSchema,
87
98
  blockingConditionSchema: () => blockingConditionSchema,
99
+ calendarEventSchema: () => calendarEventSchema,
100
+ calendarEventTimeSchema: () => calendarEventTimeSchema,
88
101
  clinicAdminOptionsSchema: () => clinicAdminOptionsSchema,
89
102
  clinicAdminSchema: () => clinicAdminSchema,
90
103
  clinicAdminSignupSchema: () => clinicAdminSignupSchema,
@@ -100,16 +113,20 @@ __export(index_exports, {
100
113
  contactPersonSchema: () => contactPersonSchema,
101
114
  contraindicationSchema: () => contraindicationSchema,
102
115
  createAdminTokenSchema: () => createAdminTokenSchema,
116
+ createAppointmentSchema: () => createAppointmentSchema,
117
+ createCalendarEventSchema: () => createCalendarEventSchema,
103
118
  createClinicAdminSchema: () => createClinicAdminSchema,
104
119
  createClinicGroupSchema: () => createClinicGroupSchema,
105
120
  createClinicSchema: () => createClinicSchema,
106
121
  createDefaultClinicGroupSchema: () => createDefaultClinicGroupSchema,
107
122
  createDocumentTemplateSchema: () => createDocumentTemplateSchema,
123
+ createDraftPractitionerSchema: () => createDraftPractitionerSchema,
108
124
  createPatientLocationInfoSchema: () => createPatientLocationInfoSchema,
109
125
  createPatientMedicalInfoSchema: () => createPatientMedicalInfoSchema,
110
126
  createPatientProfileSchema: () => createPatientProfileSchema,
111
127
  createPatientSensitiveInfoSchema: () => createPatientSensitiveInfoSchema,
112
128
  createPractitionerSchema: () => createPractitionerSchema,
129
+ createPractitionerTokenSchema: () => createPractitionerTokenSchema,
113
130
  createUserOptionsSchema: () => createUserOptionsSchema,
114
131
  doctorInfoSchema: () => doctorInfoSchema,
115
132
  documentElementSchema: () => documentElementSchema,
@@ -131,21 +148,31 @@ __export(index_exports, {
131
148
  patientDoctorSchema: () => patientDoctorSchema,
132
149
  patientLocationInfoSchema: () => patientLocationInfoSchema,
133
150
  patientMedicalInfoSchema: () => patientMedicalInfoSchema,
151
+ patientProfileInfoSchema: () => patientProfileInfoSchema,
134
152
  patientProfileSchema: () => patientProfileSchema,
135
153
  patientSensitiveInfoSchema: () => patientSensitiveInfoSchema,
136
154
  postRequirementNotificationSchema: () => postRequirementNotificationSchema,
137
155
  practitionerBasicInfoSchema: () => practitionerBasicInfoSchema,
138
156
  practitionerCertificationSchema: () => practitionerCertificationSchema,
139
157
  practitionerClinicProceduresSchema: () => practitionerClinicProceduresSchema,
158
+ practitionerClinicWorkingHoursSchema: () => practitionerClinicWorkingHoursSchema,
159
+ practitionerProfileInfoSchema: () => practitionerProfileInfoSchema,
140
160
  practitionerReviewSchema: () => practitionerReviewSchema,
141
161
  practitionerSchema: () => practitionerSchema,
162
+ practitionerTokenSchema: () => practitionerTokenSchema,
142
163
  practitionerWorkingHoursSchema: () => practitionerWorkingHoursSchema,
143
164
  preRequirementNotificationSchema: () => preRequirementNotificationSchema,
165
+ procedureCategorizationSchema: () => procedureCategorizationSchema,
166
+ procedureInfoSchema: () => procedureInfoSchema,
144
167
  reviewInfoSchema: () => reviewInfoSchema,
145
168
  serviceInfoSchema: () => serviceInfoSchema,
169
+ syncedCalendarEventSchema: () => syncedCalendarEventSchema,
170
+ timeSlotSchema: () => timeSlotSchema2,
146
171
  timestampSchema: () => timestampSchema,
147
172
  updateAllergySchema: () => updateAllergySchema,
173
+ updateAppointmentSchema: () => updateAppointmentSchema,
148
174
  updateBlockingConditionSchema: () => updateBlockingConditionSchema,
175
+ updateCalendarEventSchema: () => updateCalendarEventSchema,
149
176
  updateClinicAdminSchema: () => updateClinicAdminSchema,
150
177
  updateClinicGroupSchema: () => updateClinicGroupSchema,
151
178
  updateClinicSchema: () => updateClinicSchema,
@@ -253,6 +280,31 @@ var FilledDocumentStatus = /* @__PURE__ */ ((FilledDocumentStatus2) => {
253
280
  return FilledDocumentStatus2;
254
281
  })(FilledDocumentStatus || {});
255
282
 
283
+ // src/types/calendar/index.ts
284
+ var CalendarEventStatus = /* @__PURE__ */ ((CalendarEventStatus3) => {
285
+ CalendarEventStatus3["PENDING"] = "pending";
286
+ CalendarEventStatus3["CONFIRMED"] = "confirmed";
287
+ CalendarEventStatus3["REJECTED"] = "rejected";
288
+ CalendarEventStatus3["CANCELED"] = "canceled";
289
+ CalendarEventStatus3["RESCHEDULED"] = "rescheduled";
290
+ CalendarEventStatus3["COMPLETED"] = "completed";
291
+ return CalendarEventStatus3;
292
+ })(CalendarEventStatus || {});
293
+ var CalendarSyncStatus = /* @__PURE__ */ ((CalendarSyncStatus3) => {
294
+ CalendarSyncStatus3["INTERNAL"] = "internal";
295
+ CalendarSyncStatus3["EXTERNAL"] = "external";
296
+ return CalendarSyncStatus3;
297
+ })(CalendarSyncStatus || {});
298
+ var CalendarEventType = /* @__PURE__ */ ((CalendarEventType2) => {
299
+ CalendarEventType2["APPOINTMENT"] = "appointment";
300
+ CalendarEventType2["BLOCKING"] = "blocking";
301
+ CalendarEventType2["BREAK"] = "break";
302
+ CalendarEventType2["FREE_DAY"] = "free_day";
303
+ CalendarEventType2["OTHER"] = "other";
304
+ return CalendarEventType2;
305
+ })(CalendarEventType || {});
306
+ var CALENDAR_COLLECTION = "calendar";
307
+
256
308
  // src/types/index.ts
257
309
  var UserRole = /* @__PURE__ */ ((UserRole2) => {
258
310
  UserRole2["PATIENT"] = "patient";
@@ -1372,9 +1424,9 @@ var addAllergyUtil = async (db, patientId, data, userRef) => {
1372
1424
  var updateAllergyUtil = async (db, patientId, data, userRef) => {
1373
1425
  const validatedData = updateAllergySchema.parse(data);
1374
1426
  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();
1427
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1428
+ if (!doc20.exists()) throw new Error("Medical info not found");
1429
+ const medicalInfo = doc20.data();
1378
1430
  if (allergyIndex >= medicalInfo.allergies.length) {
1379
1431
  throw new Error("Invalid allergy index");
1380
1432
  }
@@ -1390,9 +1442,9 @@ var updateAllergyUtil = async (db, patientId, data, userRef) => {
1390
1442
  });
1391
1443
  };
1392
1444
  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();
1445
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1446
+ if (!doc20.exists()) throw new Error("Medical info not found");
1447
+ const medicalInfo = doc20.data();
1396
1448
  if (allergyIndex >= medicalInfo.allergies.length) {
1397
1449
  throw new Error("Invalid allergy index");
1398
1450
  }
@@ -1417,9 +1469,9 @@ var addBlockingConditionUtil = async (db, patientId, data, userRef) => {
1417
1469
  var updateBlockingConditionUtil = async (db, patientId, data, userRef) => {
1418
1470
  const validatedData = updateBlockingConditionSchema.parse(data);
1419
1471
  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();
1472
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1473
+ if (!doc20.exists()) throw new Error("Medical info not found");
1474
+ const medicalInfo = doc20.data();
1423
1475
  if (conditionIndex >= medicalInfo.blockingConditions.length) {
1424
1476
  throw new Error("Invalid blocking condition index");
1425
1477
  }
@@ -1435,9 +1487,9 @@ var updateBlockingConditionUtil = async (db, patientId, data, userRef) => {
1435
1487
  });
1436
1488
  };
1437
1489
  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();
1490
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1491
+ if (!doc20.exists()) throw new Error("Medical info not found");
1492
+ const medicalInfo = doc20.data();
1441
1493
  if (conditionIndex >= medicalInfo.blockingConditions.length) {
1442
1494
  throw new Error("Invalid blocking condition index");
1443
1495
  }
@@ -1462,9 +1514,9 @@ var addContraindicationUtil = async (db, patientId, data, userRef) => {
1462
1514
  var updateContraindicationUtil = async (db, patientId, data, userRef) => {
1463
1515
  const validatedData = updateContraindicationSchema.parse(data);
1464
1516
  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();
1517
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1518
+ if (!doc20.exists()) throw new Error("Medical info not found");
1519
+ const medicalInfo = doc20.data();
1468
1520
  if (contraindicationIndex >= medicalInfo.contraindications.length) {
1469
1521
  throw new Error("Invalid contraindication index");
1470
1522
  }
@@ -1480,9 +1532,9 @@ var updateContraindicationUtil = async (db, patientId, data, userRef) => {
1480
1532
  });
1481
1533
  };
1482
1534
  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();
1535
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1536
+ if (!doc20.exists()) throw new Error("Medical info not found");
1537
+ const medicalInfo = doc20.data();
1486
1538
  if (contraindicationIndex >= medicalInfo.contraindications.length) {
1487
1539
  throw new Error("Invalid contraindication index");
1488
1540
  }
@@ -1507,9 +1559,9 @@ var addMedicationUtil = async (db, patientId, data, userRef) => {
1507
1559
  var updateMedicationUtil = async (db, patientId, data, userRef) => {
1508
1560
  const validatedData = updateMedicationSchema.parse(data);
1509
1561
  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();
1562
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1563
+ if (!doc20.exists()) throw new Error("Medical info not found");
1564
+ const medicalInfo = doc20.data();
1513
1565
  if (medicationIndex >= medicalInfo.currentMedications.length) {
1514
1566
  throw new Error("Invalid medication index");
1515
1567
  }
@@ -1525,9 +1577,9 @@ var updateMedicationUtil = async (db, patientId, data, userRef) => {
1525
1577
  });
1526
1578
  };
1527
1579
  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();
1580
+ const doc20 = await (0, import_firestore5.getDoc)(getMedicalInfoDocRef(db, patientId));
1581
+ if (!doc20.exists()) throw new Error("Medical info not found");
1582
+ const medicalInfo = doc20.data();
1531
1583
  if (medicationIndex >= medicalInfo.currentMedications.length) {
1532
1584
  throw new Error("Invalid medication index");
1533
1585
  }
@@ -2320,14 +2372,14 @@ var TreatmentBenefit = /* @__PURE__ */ ((TreatmentBenefit2) => {
2320
2372
  })(TreatmentBenefit || {});
2321
2373
 
2322
2374
  // 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;
2375
+ var PricingMeasure = /* @__PURE__ */ ((PricingMeasure3) => {
2376
+ PricingMeasure3["PER_ML"] = "per_ml";
2377
+ PricingMeasure3["PER_ZONE"] = "per_zone";
2378
+ PricingMeasure3["PER_AREA"] = "per_area";
2379
+ PricingMeasure3["PER_SESSION"] = "per_session";
2380
+ PricingMeasure3["PER_TREATMENT"] = "per_treatment";
2381
+ PricingMeasure3["PER_PACKAGE"] = "per_package";
2382
+ return PricingMeasure3;
2331
2383
  })(PricingMeasure || {});
2332
2384
  var Currency = /* @__PURE__ */ ((Currency2) => {
2333
2385
  Currency2["EUR"] = "EUR";
@@ -2821,7 +2873,7 @@ async function getClinicAdminsByGroup(db, clinicGroupId) {
2821
2873
  (0, import_firestore11.where)("clinicGroupId", "==", clinicGroupId)
2822
2874
  );
2823
2875
  const querySnapshot = await (0, import_firestore11.getDocs)(q);
2824
- return querySnapshot.docs.map((doc14) => doc14.data());
2876
+ return querySnapshot.docs.map((doc20) => doc20.data());
2825
2877
  }
2826
2878
  async function updateClinicAdmin(db, adminId, data) {
2827
2879
  const admin = await getClinicAdmin(db, adminId);
@@ -3089,6 +3141,19 @@ var import_firestore13 = require("firebase/firestore");
3089
3141
 
3090
3142
  // src/types/practitioner/index.ts
3091
3143
  var PRACTITIONERS_COLLECTION = "practitioners";
3144
+ var REGISTER_TOKENS_COLLECTION = "register_tokens";
3145
+ var PractitionerStatus = /* @__PURE__ */ ((PractitionerStatus2) => {
3146
+ PractitionerStatus2["DRAFT"] = "draft";
3147
+ PractitionerStatus2["ACTIVE"] = "active";
3148
+ return PractitionerStatus2;
3149
+ })(PractitionerStatus || {});
3150
+ var PractitionerTokenStatus = /* @__PURE__ */ ((PractitionerTokenStatus2) => {
3151
+ PractitionerTokenStatus2["ACTIVE"] = "active";
3152
+ PractitionerTokenStatus2["USED"] = "used";
3153
+ PractitionerTokenStatus2["EXPIRED"] = "expired";
3154
+ PractitionerTokenStatus2["REVOKED"] = "revoked";
3155
+ return PractitionerTokenStatus2;
3156
+ })(PractitionerTokenStatus || {});
3092
3157
 
3093
3158
  // src/validations/practitioner.schema.ts
3094
3159
  var import_zod10 = require("zod");
@@ -3157,6 +3222,21 @@ var practitionerWorkingHoursSchema = import_zod10.z.object({
3157
3222
  createdAt: import_zod10.z.instanceof(import_firestore12.Timestamp),
3158
3223
  updatedAt: import_zod10.z.instanceof(import_firestore12.Timestamp)
3159
3224
  });
3225
+ var practitionerClinicWorkingHoursSchema = import_zod10.z.object({
3226
+ clinicId: import_zod10.z.string().min(1),
3227
+ workingHours: import_zod10.z.object({
3228
+ monday: timeSlotSchema,
3229
+ tuesday: timeSlotSchema,
3230
+ wednesday: timeSlotSchema,
3231
+ thursday: timeSlotSchema,
3232
+ friday: timeSlotSchema,
3233
+ saturday: timeSlotSchema,
3234
+ sunday: timeSlotSchema
3235
+ }),
3236
+ isActive: import_zod10.z.boolean(),
3237
+ createdAt: import_zod10.z.instanceof(import_firestore12.Timestamp),
3238
+ updatedAt: import_zod10.z.instanceof(import_firestore12.Timestamp)
3239
+ });
3160
3240
  var practitionerReviewSchema = import_zod10.z.object({
3161
3241
  id: import_zod10.z.string().min(1),
3162
3242
  practitionerId: import_zod10.z.string().min(1),
@@ -3182,8 +3262,10 @@ var practitionerSchema = import_zod10.z.object({
3182
3262
  basicInfo: practitionerBasicInfoSchema,
3183
3263
  certification: practitionerCertificationSchema,
3184
3264
  clinics: import_zod10.z.array(import_zod10.z.string()),
3265
+ clinicWorkingHours: import_zod10.z.array(practitionerClinicWorkingHoursSchema),
3185
3266
  isActive: import_zod10.z.boolean(),
3186
3267
  isVerified: import_zod10.z.boolean(),
3268
+ status: import_zod10.z.nativeEnum(PractitionerStatus),
3187
3269
  createdAt: import_zod10.z.instanceof(import_firestore12.Timestamp),
3188
3270
  updatedAt: import_zod10.z.instanceof(import_firestore12.Timestamp)
3189
3271
  });
@@ -3192,8 +3274,37 @@ var createPractitionerSchema = import_zod10.z.object({
3192
3274
  basicInfo: practitionerBasicInfoSchema,
3193
3275
  certification: practitionerCertificationSchema,
3194
3276
  clinics: import_zod10.z.array(import_zod10.z.string()).optional(),
3277
+ clinicWorkingHours: import_zod10.z.array(practitionerClinicWorkingHoursSchema).optional(),
3195
3278
  isActive: import_zod10.z.boolean(),
3196
- isVerified: import_zod10.z.boolean()
3279
+ isVerified: import_zod10.z.boolean(),
3280
+ status: import_zod10.z.nativeEnum(PractitionerStatus).optional()
3281
+ });
3282
+ var createDraftPractitionerSchema = import_zod10.z.object({
3283
+ basicInfo: practitionerBasicInfoSchema,
3284
+ certification: practitionerCertificationSchema,
3285
+ clinics: import_zod10.z.array(import_zod10.z.string()).optional(),
3286
+ clinicWorkingHours: import_zod10.z.array(practitionerClinicWorkingHoursSchema).optional(),
3287
+ isActive: import_zod10.z.boolean().optional().default(false),
3288
+ isVerified: import_zod10.z.boolean().optional().default(false)
3289
+ });
3290
+ var practitionerTokenSchema = import_zod10.z.object({
3291
+ id: import_zod10.z.string().min(1),
3292
+ token: import_zod10.z.string().min(6),
3293
+ practitionerId: import_zod10.z.string().min(1),
3294
+ email: import_zod10.z.string().email(),
3295
+ clinicId: import_zod10.z.string().min(1),
3296
+ status: import_zod10.z.nativeEnum(PractitionerTokenStatus),
3297
+ createdBy: import_zod10.z.string().min(1),
3298
+ createdAt: import_zod10.z.instanceof(import_firestore12.Timestamp),
3299
+ expiresAt: import_zod10.z.instanceof(import_firestore12.Timestamp),
3300
+ usedBy: import_zod10.z.string().optional(),
3301
+ usedAt: import_zod10.z.instanceof(import_firestore12.Timestamp).optional()
3302
+ });
3303
+ var createPractitionerTokenSchema = import_zod10.z.object({
3304
+ practitionerId: import_zod10.z.string().min(1),
3305
+ email: import_zod10.z.string().email(),
3306
+ clinicId: import_zod10.z.string().min(1),
3307
+ expiresAt: import_zod10.z.date().optional()
3197
3308
  });
3198
3309
 
3199
3310
  // src/services/practitioner/practitioner.service.ts
@@ -3241,8 +3352,10 @@ var PractitionerService = class extends BaseService {
3241
3352
  basicInfo: validatedData.basicInfo,
3242
3353
  certification: validatedData.certification,
3243
3354
  clinics: validatedData.clinics || [],
3355
+ clinicWorkingHours: validatedData.clinicWorkingHours || [],
3244
3356
  isActive: validatedData.isActive,
3245
3357
  isVerified: validatedData.isVerified,
3358
+ status: validatedData.status || "active" /* ACTIVE */,
3246
3359
  createdAt: (0, import_firestore13.serverTimestamp)(),
3247
3360
  updatedAt: (0, import_firestore13.serverTimestamp)()
3248
3361
  };
@@ -3267,6 +3380,200 @@ var PractitionerService = class extends BaseService {
3267
3380
  throw error;
3268
3381
  }
3269
3382
  }
3383
+ /**
3384
+ * Kreira novi draft profil zdravstvenog radnika bez povezanog korisnika
3385
+ * Koristi se od strane administratora klinike za kreiranje profila i kasnije pozivanje
3386
+ * @param data Podaci za kreiranje draft profila
3387
+ * @param createdBy ID administratora koji kreira profil
3388
+ * @param clinicId ID klinike za koju se kreira profil
3389
+ * @returns Objekt koji sadrži kreirani draft profil i token za registraciju
3390
+ */
3391
+ async createDraftPractitioner(data, createdBy, clinicId) {
3392
+ try {
3393
+ const validatedData = createDraftPractitionerSchema.parse(data);
3394
+ const clinic = await this.getClinicService().getClinic(clinicId);
3395
+ if (!clinic) {
3396
+ throw new Error(`Clinic ${clinicId} not found`);
3397
+ }
3398
+ const clinics = data.clinics || [clinicId];
3399
+ if (data.clinics) {
3400
+ for (const cId of data.clinics) {
3401
+ if (cId !== clinicId) {
3402
+ const otherClinic = await this.getClinicService().getClinic(cId);
3403
+ if (!otherClinic) {
3404
+ throw new Error(`Clinic ${cId} not found`);
3405
+ }
3406
+ }
3407
+ }
3408
+ }
3409
+ const practitionerId = this.generateId();
3410
+ const practitionerData = {
3411
+ id: practitionerId,
3412
+ userRef: "",
3413
+ // Prazno - biće popunjeno kada korisnik kreira nalog
3414
+ basicInfo: validatedData.basicInfo,
3415
+ certification: validatedData.certification,
3416
+ clinics,
3417
+ clinicWorkingHours: validatedData.clinicWorkingHours || [],
3418
+ isActive: validatedData.isActive !== void 0 ? validatedData.isActive : false,
3419
+ isVerified: validatedData.isVerified !== void 0 ? validatedData.isVerified : false,
3420
+ status: "draft" /* DRAFT */,
3421
+ createdAt: (0, import_firestore13.serverTimestamp)(),
3422
+ updatedAt: (0, import_firestore13.serverTimestamp)()
3423
+ };
3424
+ practitionerSchema.parse({
3425
+ ...practitionerData,
3426
+ userRef: "temp-for-validation",
3427
+ createdAt: import_firestore13.Timestamp.now(),
3428
+ updatedAt: import_firestore13.Timestamp.now()
3429
+ });
3430
+ await (0, import_firestore13.setDoc)(
3431
+ (0, import_firestore13.doc)(this.db, PRACTITIONERS_COLLECTION, practitionerData.id),
3432
+ practitionerData
3433
+ );
3434
+ const savedPractitioner = await this.getPractitioner(practitionerData.id);
3435
+ if (!savedPractitioner) {
3436
+ throw new Error("Failed to create draft practitioner profile");
3437
+ }
3438
+ const tokenString = this.generateId().slice(0, 6).toUpperCase();
3439
+ const expiration = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
3440
+ const token = {
3441
+ id: this.generateId(),
3442
+ token: tokenString,
3443
+ practitionerId,
3444
+ email: practitionerData.basicInfo.email,
3445
+ clinicId,
3446
+ status: "active" /* ACTIVE */,
3447
+ createdBy,
3448
+ createdAt: import_firestore13.Timestamp.now(),
3449
+ expiresAt: import_firestore13.Timestamp.fromDate(expiration)
3450
+ };
3451
+ practitionerTokenSchema.parse(token);
3452
+ const tokenPath = `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
3453
+ await (0, import_firestore13.setDoc)((0, import_firestore13.doc)(this.db, tokenPath), token);
3454
+ return { practitioner: savedPractitioner, token };
3455
+ } catch (error) {
3456
+ if (error instanceof import_zod11.z.ZodError) {
3457
+ throw new Error("Invalid practitioner data: " + error.message);
3458
+ }
3459
+ throw error;
3460
+ }
3461
+ }
3462
+ /**
3463
+ * Creates a token for inviting practitioner to claim their profile
3464
+ * @param data Data for creating token
3465
+ * @param createdBy ID of the user creating the token
3466
+ * @returns Created token
3467
+ */
3468
+ async createPractitionerToken(data, createdBy) {
3469
+ try {
3470
+ const validatedData = createPractitionerTokenSchema.parse(data);
3471
+ const practitioner = await this.getPractitioner(
3472
+ validatedData.practitionerId
3473
+ );
3474
+ if (!practitioner) {
3475
+ throw new Error("Practitioner not found");
3476
+ }
3477
+ if (practitioner.status !== "draft" /* DRAFT */) {
3478
+ throw new Error(
3479
+ "Can only create tokens for practitioners in DRAFT status"
3480
+ );
3481
+ }
3482
+ const clinic = await this.getClinicService().getClinic(
3483
+ validatedData.clinicId
3484
+ );
3485
+ if (!clinic) {
3486
+ throw new Error(`Clinic ${validatedData.clinicId} not found`);
3487
+ }
3488
+ if (!practitioner.clinics.includes(validatedData.clinicId)) {
3489
+ throw new Error("Practitioner is not associated with this clinic");
3490
+ }
3491
+ const expiration = validatedData.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
3492
+ const tokenString = this.generateId().slice(0, 6).toUpperCase();
3493
+ const token = {
3494
+ id: this.generateId(),
3495
+ token: tokenString,
3496
+ practitionerId: validatedData.practitionerId,
3497
+ email: validatedData.email,
3498
+ clinicId: validatedData.clinicId,
3499
+ status: "active" /* ACTIVE */,
3500
+ createdBy,
3501
+ createdAt: import_firestore13.Timestamp.now(),
3502
+ expiresAt: import_firestore13.Timestamp.fromDate(expiration)
3503
+ };
3504
+ practitionerTokenSchema.parse(token);
3505
+ const tokenPath = `${PRACTITIONERS_COLLECTION}/${validatedData.practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
3506
+ await (0, import_firestore13.setDoc)((0, import_firestore13.doc)(this.db, tokenPath), token);
3507
+ return token;
3508
+ } catch (error) {
3509
+ if (error instanceof import_zod11.z.ZodError) {
3510
+ throw new Error("Invalid token data: " + error.message);
3511
+ }
3512
+ throw error;
3513
+ }
3514
+ }
3515
+ /**
3516
+ * Gets active tokens for a practitioner
3517
+ * @param practitionerId ID of the practitioner
3518
+ * @returns Array of active tokens
3519
+ */
3520
+ async getPractitionerActiveTokens(practitionerId) {
3521
+ const tokensRef = (0, import_firestore13.collection)(
3522
+ this.db,
3523
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
3524
+ );
3525
+ const q = (0, import_firestore13.query)(
3526
+ tokensRef,
3527
+ (0, import_firestore13.where)("status", "==", "active" /* ACTIVE */),
3528
+ (0, import_firestore13.where)("expiresAt", ">", import_firestore13.Timestamp.now())
3529
+ );
3530
+ const querySnapshot = await (0, import_firestore13.getDocs)(q);
3531
+ return querySnapshot.docs.map((doc20) => doc20.data());
3532
+ }
3533
+ /**
3534
+ * Gets a token by its string value and validates it
3535
+ * @param tokenString The token string to find
3536
+ * @returns The token if found and valid, null otherwise
3537
+ */
3538
+ async validateToken(tokenString) {
3539
+ const practitionersRef = (0, import_firestore13.collection)(this.db, PRACTITIONERS_COLLECTION);
3540
+ const practitionersSnapshot = await (0, import_firestore13.getDocs)(practitionersRef);
3541
+ for (const practitionerDoc of practitionersSnapshot.docs) {
3542
+ const practitionerId = practitionerDoc.id;
3543
+ const tokensRef = (0, import_firestore13.collection)(
3544
+ this.db,
3545
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
3546
+ );
3547
+ const q = (0, import_firestore13.query)(
3548
+ tokensRef,
3549
+ (0, import_firestore13.where)("token", "==", tokenString),
3550
+ (0, import_firestore13.where)("status", "==", "active" /* ACTIVE */),
3551
+ (0, import_firestore13.where)("expiresAt", ">", import_firestore13.Timestamp.now())
3552
+ );
3553
+ const tokenSnapshot = await (0, import_firestore13.getDocs)(q);
3554
+ if (!tokenSnapshot.empty) {
3555
+ return tokenSnapshot.docs[0].data();
3556
+ }
3557
+ }
3558
+ return null;
3559
+ }
3560
+ /**
3561
+ * Marks a token as used
3562
+ * @param tokenId ID of the token
3563
+ * @param practitionerId ID of the practitioner
3564
+ * @param userId ID of the user using the token
3565
+ */
3566
+ async markTokenAsUsed(tokenId, practitionerId, userId) {
3567
+ const tokenRef = (0, import_firestore13.doc)(
3568
+ this.db,
3569
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${tokenId}`
3570
+ );
3571
+ await (0, import_firestore13.updateDoc)(tokenRef, {
3572
+ status: "used" /* USED */,
3573
+ usedBy: userId,
3574
+ usedAt: import_firestore13.Timestamp.now()
3575
+ });
3576
+ }
3270
3577
  /**
3271
3578
  * Dohvata zdravstvenog radnika po ID-u
3272
3579
  */
@@ -3300,10 +3607,23 @@ var PractitionerService = class extends BaseService {
3300
3607
  const q = (0, import_firestore13.query)(
3301
3608
  (0, import_firestore13.collection)(this.db, PRACTITIONERS_COLLECTION),
3302
3609
  (0, import_firestore13.where)("clinics", "array-contains", clinicId),
3303
- (0, import_firestore13.where)("isActive", "==", true)
3610
+ (0, import_firestore13.where)("isActive", "==", true),
3611
+ (0, import_firestore13.where)("status", "==", "active" /* ACTIVE */)
3612
+ );
3613
+ const querySnapshot = await (0, import_firestore13.getDocs)(q);
3614
+ return querySnapshot.docs.map((doc20) => doc20.data());
3615
+ }
3616
+ /**
3617
+ * Dohvata sve draft zdravstvene radnike za određenu kliniku
3618
+ */
3619
+ async getDraftPractitionersByClinic(clinicId) {
3620
+ const q = (0, import_firestore13.query)(
3621
+ (0, import_firestore13.collection)(this.db, PRACTITIONERS_COLLECTION),
3622
+ (0, import_firestore13.where)("clinics", "array-contains", clinicId),
3623
+ (0, import_firestore13.where)("status", "==", "draft" /* DRAFT */)
3304
3624
  );
3305
3625
  const querySnapshot = await (0, import_firestore13.getDocs)(q);
3306
- return querySnapshot.docs.map((doc14) => doc14.data());
3626
+ return querySnapshot.docs.map((doc20) => doc20.data());
3307
3627
  }
3308
3628
  /**
3309
3629
  * Ažurira profil zdravstvenog radnika
@@ -3409,6 +3729,35 @@ var PractitionerService = class extends BaseService {
3409
3729
  }
3410
3730
  await (0, import_firestore13.deleteDoc)((0, import_firestore13.doc)(this.db, PRACTITIONERS_COLLECTION, practitionerId));
3411
3731
  }
3732
+ /**
3733
+ * Validates a registration token and claims the associated draft practitioner profile
3734
+ * @param tokenString The token provided by the practitioner
3735
+ * @param userId The ID of the user claiming the profile
3736
+ * @returns The claimed practitioner profile or null if token is invalid
3737
+ */
3738
+ async validateTokenAndClaimProfile(tokenString, userId) {
3739
+ const token = await this.validateToken(tokenString);
3740
+ if (!token) {
3741
+ return null;
3742
+ }
3743
+ const practitioner = await this.getPractitioner(token.practitionerId);
3744
+ if (!practitioner) {
3745
+ return null;
3746
+ }
3747
+ if (practitioner.status !== "draft" /* DRAFT */) {
3748
+ throw new Error("This practitioner profile has already been claimed");
3749
+ }
3750
+ const existingPractitioner = await this.getPractitionerByUserRef(userId);
3751
+ if (existingPractitioner) {
3752
+ throw new Error("User already has a practitioner profile");
3753
+ }
3754
+ const updatedPractitioner = await this.updatePractitioner(practitioner.id, {
3755
+ userRef: userId,
3756
+ status: "active" /* ACTIVE */
3757
+ });
3758
+ await this.markTokenAsUsed(token.id, token.practitionerId, userId);
3759
+ return updatedPractitioner;
3760
+ }
3412
3761
  };
3413
3762
 
3414
3763
  // src/services/user.service.ts
@@ -3583,7 +3932,7 @@ var UserService = class extends BaseService {
3583
3932
  ];
3584
3933
  const q = (0, import_firestore14.query)((0, import_firestore14.collection)(this.db, USERS_COLLECTION), ...constraints);
3585
3934
  const querySnapshot = await (0, import_firestore14.getDocs)(q);
3586
- const users = querySnapshot.docs.map((doc14) => doc14.data());
3935
+ const users = querySnapshot.docs.map((doc20) => doc20.data());
3587
3936
  return Promise.all(users.map((userData) => userSchema.parse(userData)));
3588
3937
  }
3589
3938
  /**
@@ -3966,7 +4315,7 @@ async function getAllActiveGroups(db) {
3966
4315
  (0, import_firestore15.where)("isActive", "==", true)
3967
4316
  );
3968
4317
  const querySnapshot = await (0, import_firestore15.getDocs)(q);
3969
- return querySnapshot.docs.map((doc14) => doc14.data());
4318
+ return querySnapshot.docs.map((doc20) => doc20.data());
3970
4319
  }
3971
4320
  async function updateClinicGroup(db, groupId, data, app) {
3972
4321
  console.log("[CLINIC_GROUP] Updating clinic group", { groupId });
@@ -4626,7 +4975,7 @@ async function getClinicsByGroup(db, groupId) {
4626
4975
  (0, import_firestore16.where)("isActive", "==", true)
4627
4976
  );
4628
4977
  const querySnapshot = await (0, import_firestore16.getDocs)(q);
4629
- return querySnapshot.docs.map((doc14) => doc14.data());
4978
+ return querySnapshot.docs.map((doc20) => doc20.data());
4630
4979
  }
4631
4980
  async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app) {
4632
4981
  console.log("[CLINIC] Starting clinic update", { clinicId, adminId });
@@ -4838,7 +5187,7 @@ async function getClinicsByAdmin(db, adminId, options = {}, clinicAdminService,
4838
5187
  }
4839
5188
  const q = (0, import_firestore16.query)((0, import_firestore16.collection)(db, CLINICS_COLLECTION), ...constraints);
4840
5189
  const querySnapshot = await (0, import_firestore16.getDocs)(q);
4841
- return querySnapshot.docs.map((doc14) => doc14.data());
5190
+ return querySnapshot.docs.map((doc20) => doc20.data());
4842
5191
  }
4843
5192
  async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGroupService) {
4844
5193
  return getClinicsByAdmin(
@@ -4980,8 +5329,8 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
4980
5329
  }
4981
5330
  const q = (0, import_firestore18.query)((0, import_firestore18.collection)(db, CLINICS_COLLECTION), ...constraints);
4982
5331
  const querySnapshot = await (0, import_firestore18.getDocs)(q);
4983
- for (const doc14 of querySnapshot.docs) {
4984
- const clinic = doc14.data();
5332
+ for (const doc20 of querySnapshot.docs) {
5333
+ const clinic = doc20.data();
4985
5334
  const distance = (0, import_geofire_common4.distanceBetween)(
4986
5335
  [center.latitude, center.longitude],
4987
5336
  [clinic.location.latitude, clinic.location.longitude]
@@ -5842,9 +6191,9 @@ var NotificationService = class extends BaseService {
5842
6191
  (0, import_firestore20.orderBy)("notificationTime", "desc")
5843
6192
  );
5844
6193
  const querySnapshot = await (0, import_firestore20.getDocs)(q);
5845
- return querySnapshot.docs.map((doc14) => ({
5846
- id: doc14.id,
5847
- ...doc14.data()
6194
+ return querySnapshot.docs.map((doc20) => ({
6195
+ id: doc20.id,
6196
+ ...doc20.data()
5848
6197
  }));
5849
6198
  }
5850
6199
  /**
@@ -5858,9 +6207,9 @@ var NotificationService = class extends BaseService {
5858
6207
  (0, import_firestore20.orderBy)("notificationTime", "desc")
5859
6208
  );
5860
6209
  const querySnapshot = await (0, import_firestore20.getDocs)(q);
5861
- return querySnapshot.docs.map((doc14) => ({
5862
- id: doc14.id,
5863
- ...doc14.data()
6210
+ return querySnapshot.docs.map((doc20) => ({
6211
+ id: doc20.id,
6212
+ ...doc20.data()
5864
6213
  }));
5865
6214
  }
5866
6215
  /**
@@ -5932,9 +6281,9 @@ var NotificationService = class extends BaseService {
5932
6281
  (0, import_firestore20.orderBy)("notificationTime", "desc")
5933
6282
  );
5934
6283
  const querySnapshot = await (0, import_firestore20.getDocs)(q);
5935
- return querySnapshot.docs.map((doc14) => ({
5936
- id: doc14.id,
5937
- ...doc14.data()
6284
+ return querySnapshot.docs.map((doc20) => ({
6285
+ id: doc20.id,
6286
+ ...doc20.data()
5938
6287
  }));
5939
6288
  }
5940
6289
  /**
@@ -5947,9 +6296,9 @@ var NotificationService = class extends BaseService {
5947
6296
  (0, import_firestore20.orderBy)("notificationTime", "desc")
5948
6297
  );
5949
6298
  const querySnapshot = await (0, import_firestore20.getDocs)(q);
5950
- return querySnapshot.docs.map((doc14) => ({
5951
- id: doc14.id,
5952
- ...doc14.data()
6299
+ return querySnapshot.docs.map((doc20) => ({
6300
+ id: doc20.id,
6301
+ ...doc20.data()
5953
6302
  }));
5954
6303
  }
5955
6304
  };
@@ -6066,9 +6415,9 @@ var DocumentationTemplateService = class extends BaseService {
6066
6415
  const querySnapshot = await (0, import_firestore21.getDocs)(q);
6067
6416
  const templates = [];
6068
6417
  let lastVisible = null;
6069
- querySnapshot.forEach((doc14) => {
6070
- templates.push(doc14.data());
6071
- lastVisible = doc14;
6418
+ querySnapshot.forEach((doc20) => {
6419
+ templates.push(doc20.data());
6420
+ lastVisible = doc20;
6072
6421
  });
6073
6422
  return {
6074
6423
  templates,
@@ -6096,9 +6445,9 @@ var DocumentationTemplateService = class extends BaseService {
6096
6445
  const querySnapshot = await (0, import_firestore21.getDocs)(q);
6097
6446
  const templates = [];
6098
6447
  let lastVisible = null;
6099
- querySnapshot.forEach((doc14) => {
6100
- templates.push(doc14.data());
6101
- lastVisible = doc14;
6448
+ querySnapshot.forEach((doc20) => {
6449
+ templates.push(doc20.data());
6450
+ lastVisible = doc20;
6102
6451
  });
6103
6452
  return {
6104
6453
  templates,
@@ -6125,9 +6474,9 @@ var DocumentationTemplateService = class extends BaseService {
6125
6474
  const querySnapshot = await (0, import_firestore21.getDocs)(q);
6126
6475
  const templates = [];
6127
6476
  let lastVisible = null;
6128
- querySnapshot.forEach((doc14) => {
6129
- templates.push(doc14.data());
6130
- lastVisible = doc14;
6477
+ querySnapshot.forEach((doc20) => {
6478
+ templates.push(doc20.data());
6479
+ lastVisible = doc20;
6131
6480
  });
6132
6481
  return {
6133
6482
  templates,
@@ -6240,9 +6589,9 @@ var FilledDocumentService = class extends BaseService {
6240
6589
  const querySnapshot = await (0, import_firestore22.getDocs)(q);
6241
6590
  const documents = [];
6242
6591
  let lastVisible = null;
6243
- querySnapshot.forEach((doc14) => {
6244
- documents.push(doc14.data());
6245
- lastVisible = doc14;
6592
+ querySnapshot.forEach((doc20) => {
6593
+ documents.push(doc20.data());
6594
+ lastVisible = doc20;
6246
6595
  });
6247
6596
  return {
6248
6597
  documents,
@@ -6269,9 +6618,9 @@ var FilledDocumentService = class extends BaseService {
6269
6618
  const querySnapshot = await (0, import_firestore22.getDocs)(q);
6270
6619
  const documents = [];
6271
6620
  let lastVisible = null;
6272
- querySnapshot.forEach((doc14) => {
6273
- documents.push(doc14.data());
6274
- lastVisible = doc14;
6621
+ querySnapshot.forEach((doc20) => {
6622
+ documents.push(doc20.data());
6623
+ lastVisible = doc20;
6275
6624
  });
6276
6625
  return {
6277
6626
  documents,
@@ -6298,9 +6647,9 @@ var FilledDocumentService = class extends BaseService {
6298
6647
  const querySnapshot = await (0, import_firestore22.getDocs)(q);
6299
6648
  const documents = [];
6300
6649
  let lastVisible = null;
6301
- querySnapshot.forEach((doc14) => {
6302
- documents.push(doc14.data());
6303
- lastVisible = doc14;
6650
+ querySnapshot.forEach((doc20) => {
6651
+ documents.push(doc20.data());
6652
+ lastVisible = doc20;
6304
6653
  });
6305
6654
  return {
6306
6655
  documents,
@@ -6327,9 +6676,9 @@ var FilledDocumentService = class extends BaseService {
6327
6676
  const querySnapshot = await (0, import_firestore22.getDocs)(q);
6328
6677
  const documents = [];
6329
6678
  let lastVisible = null;
6330
- querySnapshot.forEach((doc14) => {
6331
- documents.push(doc14.data());
6332
- lastVisible = doc14;
6679
+ querySnapshot.forEach((doc20) => {
6680
+ documents.push(doc20.data());
6681
+ lastVisible = doc20;
6333
6682
  });
6334
6683
  return {
6335
6684
  documents,
@@ -6356,9 +6705,9 @@ var FilledDocumentService = class extends BaseService {
6356
6705
  const querySnapshot = await (0, import_firestore22.getDocs)(q);
6357
6706
  const documents = [];
6358
6707
  let lastVisible = null;
6359
- querySnapshot.forEach((doc14) => {
6360
- documents.push(doc14.data());
6361
- lastVisible = doc14;
6708
+ querySnapshot.forEach((doc20) => {
6709
+ documents.push(doc20.data());
6710
+ lastVisible = doc20;
6362
6711
  });
6363
6712
  return {
6364
6713
  documents,
@@ -6367,55 +6716,2597 @@ var FilledDocumentService = class extends BaseService {
6367
6716
  }
6368
6717
  };
6369
6718
 
6370
- // src/validations/notification.schema.ts
6719
+ // src/services/calendar/calendar-refactored.service.ts
6720
+ var import_firestore31 = require("firebase/firestore");
6721
+
6722
+ // src/types/calendar/synced-calendar.types.ts
6723
+ var SyncedCalendarProvider = /* @__PURE__ */ ((SyncedCalendarProvider3) => {
6724
+ SyncedCalendarProvider3["GOOGLE"] = "google";
6725
+ SyncedCalendarProvider3["OUTLOOK"] = "outlook";
6726
+ SyncedCalendarProvider3["APPLE"] = "apple";
6727
+ return SyncedCalendarProvider3;
6728
+ })(SyncedCalendarProvider || {});
6729
+ var SYNCED_CALENDARS_COLLECTION = "syncedCalendars";
6730
+
6731
+ // src/services/calendar/calendar-refactored.service.ts
6732
+ var import_firestore32 = require("firebase/firestore");
6733
+
6734
+ // src/validations/calendar.schema.ts
6735
+ var import_zod17 = require("zod");
6736
+ var import_firestore24 = require("firebase/firestore");
6737
+
6738
+ // src/validations/profile-info.schema.ts
6371
6739
  var import_zod16 = require("zod");
6372
- var baseNotificationSchema = import_zod16.z.object({
6373
- id: import_zod16.z.string().optional(),
6374
- userId: import_zod16.z.string(),
6375
- notificationTime: import_zod16.z.any(),
6740
+ var import_firestore23 = require("firebase/firestore");
6741
+ var clinicInfoSchema2 = import_zod16.z.object({
6742
+ id: import_zod16.z.string(),
6743
+ featuredPhoto: import_zod16.z.string(),
6744
+ name: import_zod16.z.string(),
6745
+ description: import_zod16.z.string(),
6746
+ location: clinicLocationSchema,
6747
+ contactInfo: clinicContactInfoSchema
6748
+ });
6749
+ var practitionerProfileInfoSchema = import_zod16.z.object({
6750
+ id: import_zod16.z.string(),
6751
+ practitionerPhoto: import_zod16.z.string().nullable(),
6752
+ name: import_zod16.z.string(),
6753
+ email: import_zod16.z.string().email(),
6754
+ phone: import_zod16.z.string().nullable(),
6755
+ certification: practitionerCertificationSchema
6756
+ });
6757
+ var patientProfileInfoSchema = import_zod16.z.object({
6758
+ id: import_zod16.z.string(),
6759
+ fullName: import_zod16.z.string(),
6760
+ email: import_zod16.z.string().email(),
6761
+ phone: import_zod16.z.string().nullable(),
6762
+ dateOfBirth: import_zod16.z.instanceof(import_firestore23.Timestamp),
6763
+ gender: import_zod16.z.nativeEnum(Gender)
6764
+ });
6765
+
6766
+ // src/validations/calendar.schema.ts
6767
+ var MIN_APPOINTMENT_DURATION = 15;
6768
+ var calendarEventTimeSchema = import_zod17.z.object({
6769
+ start: import_zod17.z.instanceof(Date).or(import_zod17.z.instanceof(import_firestore24.Timestamp)),
6770
+ end: import_zod17.z.instanceof(Date).or(import_zod17.z.instanceof(import_firestore24.Timestamp))
6771
+ }).refine(
6772
+ (data) => {
6773
+ const startDate = data.start instanceof import_firestore24.Timestamp ? data.start.toDate() : data.start;
6774
+ const endDate = data.end instanceof import_firestore24.Timestamp ? data.end.toDate() : data.end;
6775
+ return startDate < endDate;
6776
+ },
6777
+ {
6778
+ message: "End time must be after start time",
6779
+ path: ["end"]
6780
+ }
6781
+ ).refine(
6782
+ (data) => {
6783
+ const startDate = data.start instanceof import_firestore24.Timestamp ? data.start.toDate() : data.start;
6784
+ return startDate > /* @__PURE__ */ new Date();
6785
+ },
6786
+ {
6787
+ message: "Appointment must be scheduled in the future",
6788
+ path: ["start"]
6789
+ }
6790
+ );
6791
+ var timeSlotSchema2 = import_zod17.z.object({
6792
+ start: import_zod17.z.date(),
6793
+ end: import_zod17.z.date(),
6794
+ isAvailable: import_zod17.z.boolean()
6795
+ }).refine((data) => data.start < data.end, {
6796
+ message: "End time must be after start time",
6797
+ path: ["end"]
6798
+ });
6799
+ var syncedCalendarEventSchema = import_zod17.z.object({
6800
+ eventId: import_zod17.z.string(),
6801
+ syncedCalendarProvider: import_zod17.z.nativeEnum(SyncedCalendarProvider),
6802
+ syncedAt: import_zod17.z.instanceof(Date).or(import_zod17.z.instanceof(import_firestore24.Timestamp))
6803
+ });
6804
+ var procedureInfoSchema = import_zod17.z.object({
6805
+ name: import_zod17.z.string(),
6806
+ description: import_zod17.z.string(),
6807
+ duration: import_zod17.z.number().min(MIN_APPOINTMENT_DURATION),
6808
+ price: import_zod17.z.number().min(0),
6809
+ currency: import_zod17.z.nativeEnum(Currency)
6810
+ });
6811
+ var procedureCategorizationSchema = import_zod17.z.object({
6812
+ procedureFamily: import_zod17.z.string(),
6813
+ // Replace with proper enum when available
6814
+ procedureCategory: import_zod17.z.string(),
6815
+ // Replace with proper enum when available
6816
+ procedureSubcategory: import_zod17.z.string(),
6817
+ // Replace with proper enum when available
6818
+ procedureTechnology: import_zod17.z.string(),
6819
+ // Replace with proper enum when available
6820
+ procedureProduct: import_zod17.z.string()
6821
+ // Replace with proper enum when available
6822
+ });
6823
+ var createAppointmentSchema = import_zod17.z.object({
6824
+ clinicId: import_zod17.z.string().min(1, "Clinic ID is required"),
6825
+ doctorId: import_zod17.z.string().min(1, "Doctor ID is required"),
6826
+ patientId: import_zod17.z.string().min(1, "Patient ID is required"),
6827
+ procedureId: import_zod17.z.string().min(1, "Procedure ID is required"),
6828
+ eventLocation: clinicLocationSchema,
6829
+ eventTime: calendarEventTimeSchema,
6830
+ description: import_zod17.z.string().optional()
6831
+ }).refine(
6832
+ (data) => {
6833
+ return true;
6834
+ },
6835
+ {
6836
+ message: "Invalid appointment parameters"
6837
+ }
6838
+ );
6839
+ var updateAppointmentSchema = import_zod17.z.object({
6840
+ appointmentId: import_zod17.z.string().min(1, "Appointment ID is required"),
6841
+ clinicId: import_zod17.z.string().min(1, "Clinic ID is required"),
6842
+ doctorId: import_zod17.z.string().min(1, "Doctor ID is required"),
6843
+ patientId: import_zod17.z.string().min(1, "Patient ID is required"),
6844
+ eventTime: calendarEventTimeSchema.optional(),
6845
+ description: import_zod17.z.string().optional(),
6846
+ status: import_zod17.z.nativeEnum(CalendarEventStatus).optional()
6847
+ });
6848
+ var createCalendarEventSchema = import_zod17.z.object({
6849
+ id: import_zod17.z.string(),
6850
+ clinicBranchId: import_zod17.z.string().nullable().optional(),
6851
+ clinicBranchInfo: import_zod17.z.any().nullable().optional(),
6852
+ practitionerProfileId: import_zod17.z.string().nullable().optional(),
6853
+ practitionerProfileInfo: practitionerProfileInfoSchema.nullable().optional(),
6854
+ patientProfileId: import_zod17.z.string().nullable().optional(),
6855
+ patientProfileInfo: patientProfileInfoSchema.nullable().optional(),
6856
+ procedureId: import_zod17.z.string().nullable().optional(),
6857
+ appointmentId: import_zod17.z.string().nullable().optional(),
6858
+ syncedCalendarEventId: import_zod17.z.array(syncedCalendarEventSchema).nullable().optional(),
6859
+ eventName: import_zod17.z.string().min(1, "Event name is required"),
6860
+ eventLocation: clinicLocationSchema.optional(),
6861
+ eventTime: calendarEventTimeSchema,
6862
+ description: import_zod17.z.string().optional(),
6863
+ status: import_zod17.z.nativeEnum(CalendarEventStatus),
6864
+ syncStatus: import_zod17.z.nativeEnum(CalendarSyncStatus),
6865
+ eventType: import_zod17.z.nativeEnum(CalendarEventType),
6866
+ createdAt: import_zod17.z.any(),
6867
+ // FieldValue for server timestamp
6868
+ updatedAt: import_zod17.z.any()
6869
+ // FieldValue for server timestamp
6870
+ });
6871
+ var updateCalendarEventSchema = import_zod17.z.object({
6872
+ syncedCalendarEventId: import_zod17.z.array(syncedCalendarEventSchema).nullable().optional(),
6873
+ appointmentId: import_zod17.z.string().nullable().optional(),
6874
+ eventName: import_zod17.z.string().optional(),
6875
+ eventTime: calendarEventTimeSchema.optional(),
6876
+ description: import_zod17.z.string().optional(),
6877
+ status: import_zod17.z.nativeEnum(CalendarEventStatus).optional(),
6878
+ syncStatus: import_zod17.z.nativeEnum(CalendarSyncStatus).optional(),
6879
+ eventType: import_zod17.z.nativeEnum(CalendarEventType).optional(),
6880
+ updatedAt: import_zod17.z.any()
6881
+ // FieldValue for server timestamp
6882
+ });
6883
+ var calendarEventSchema = import_zod17.z.object({
6884
+ id: import_zod17.z.string(),
6885
+ clinicBranchId: import_zod17.z.string().nullable().optional(),
6886
+ clinicBranchInfo: import_zod17.z.any().nullable().optional(),
6887
+ // Will be replaced with proper clinic info schema
6888
+ practitionerProfileId: import_zod17.z.string().nullable().optional(),
6889
+ practitionerProfileInfo: practitionerProfileInfoSchema.nullable().optional(),
6890
+ patientProfileId: import_zod17.z.string().nullable().optional(),
6891
+ patientProfileInfo: patientProfileInfoSchema.nullable().optional(),
6892
+ procedureId: import_zod17.z.string().nullable().optional(),
6893
+ procedureInfo: procedureInfoSchema.nullable().optional(),
6894
+ procedureCategorization: procedureCategorizationSchema.nullable().optional(),
6895
+ appointmentId: import_zod17.z.string().nullable().optional(),
6896
+ syncedCalendarEventId: import_zod17.z.array(syncedCalendarEventSchema).nullable().optional(),
6897
+ eventName: import_zod17.z.string(),
6898
+ eventLocation: clinicLocationSchema.optional(),
6899
+ eventTime: calendarEventTimeSchema,
6900
+ description: import_zod17.z.string().optional(),
6901
+ status: import_zod17.z.nativeEnum(CalendarEventStatus),
6902
+ syncStatus: import_zod17.z.nativeEnum(CalendarSyncStatus),
6903
+ eventType: import_zod17.z.nativeEnum(CalendarEventType),
6904
+ createdAt: import_zod17.z.instanceof(Date).or(import_zod17.z.instanceof(import_firestore24.Timestamp)),
6905
+ updatedAt: import_zod17.z.instanceof(Date).or(import_zod17.z.instanceof(import_firestore24.Timestamp))
6906
+ });
6907
+
6908
+ // src/services/calendar/utils/clinic.utils.ts
6909
+ var import_firestore26 = require("firebase/firestore");
6910
+
6911
+ // src/services/calendar/utils/docs.utils.ts
6912
+ var import_firestore25 = require("firebase/firestore");
6913
+ function getPractitionerCalendarEventDocRef(db, practitionerId, eventId) {
6914
+ return (0, import_firestore25.doc)(
6915
+ db,
6916
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${CALENDAR_COLLECTION}/${eventId}`
6917
+ );
6918
+ }
6919
+ function getPatientCalendarEventDocRef(db, patientId, eventId) {
6920
+ return (0, import_firestore25.doc)(
6921
+ db,
6922
+ `${PATIENTS_COLLECTION}/${patientId}/${CALENDAR_COLLECTION}/${eventId}`
6923
+ );
6924
+ }
6925
+ function getClinicCalendarEventDocRef(db, clinicId, eventId) {
6926
+ return (0, import_firestore25.doc)(
6927
+ db,
6928
+ `${CLINICS_COLLECTION}/${clinicId}/${CALENDAR_COLLECTION}/${eventId}`
6929
+ );
6930
+ }
6931
+ function getPractitionerSyncedCalendarDocRef(db, practitionerId, syncedCalendarId) {
6932
+ return (0, import_firestore25.doc)(
6933
+ db,
6934
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/syncedCalendars/${syncedCalendarId}`
6935
+ );
6936
+ }
6937
+ function getPatientSyncedCalendarDocRef(db, patientId, syncedCalendarId) {
6938
+ return (0, import_firestore25.doc)(
6939
+ db,
6940
+ `${PATIENTS_COLLECTION}/${patientId}/syncedCalendars/${syncedCalendarId}`
6941
+ );
6942
+ }
6943
+ function getClinicSyncedCalendarDocRef(db, clinicId, syncedCalendarId) {
6944
+ return (0, import_firestore25.doc)(
6945
+ db,
6946
+ `${CLINICS_COLLECTION}/${clinicId}/syncedCalendars/${syncedCalendarId}`
6947
+ );
6948
+ }
6949
+
6950
+ // src/services/calendar/utils/clinic.utils.ts
6951
+ async function createClinicCalendarEventUtil(db, clinicId, eventData, generateId2) {
6952
+ const eventId = generateId2();
6953
+ const eventRef = getClinicCalendarEventDocRef(db, clinicId, eventId);
6954
+ const newEvent = {
6955
+ id: eventId,
6956
+ ...eventData,
6957
+ createdAt: (0, import_firestore26.serverTimestamp)(),
6958
+ updatedAt: (0, import_firestore26.serverTimestamp)()
6959
+ };
6960
+ await (0, import_firestore26.setDoc)(eventRef, newEvent);
6961
+ return {
6962
+ ...newEvent,
6963
+ createdAt: import_firestore26.Timestamp.now(),
6964
+ updatedAt: import_firestore26.Timestamp.now()
6965
+ };
6966
+ }
6967
+ async function updateClinicCalendarEventUtil(db, clinicId, eventId, updateData) {
6968
+ const eventRef = getClinicCalendarEventDocRef(db, clinicId, eventId);
6969
+ const updates = {
6970
+ ...updateData,
6971
+ updatedAt: (0, import_firestore26.serverTimestamp)()
6972
+ };
6973
+ await (0, import_firestore26.updateDoc)(eventRef, updates);
6974
+ const updatedDoc = await (0, import_firestore26.getDoc)(eventRef);
6975
+ if (!updatedDoc.exists()) {
6976
+ throw new Error("Event not found after update");
6977
+ }
6978
+ return updatedDoc.data();
6979
+ }
6980
+ async function checkAutoConfirmAppointmentsUtil(db, clinicId) {
6981
+ const clinicDoc = await (0, import_firestore26.getDoc)((0, import_firestore26.doc)(db, `clinics/${clinicId}`));
6982
+ if (!clinicDoc.exists()) {
6983
+ throw new Error(`Clinic with ID ${clinicId} not found`);
6984
+ }
6985
+ const clinicData = clinicDoc.data();
6986
+ const clinicGroupId = clinicData.clinicGroupId;
6987
+ if (!clinicGroupId) {
6988
+ return false;
6989
+ }
6990
+ const clinicGroupDoc = await (0, import_firestore26.getDoc)(
6991
+ (0, import_firestore26.doc)(db, `${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}`)
6992
+ );
6993
+ if (!clinicGroupDoc.exists()) {
6994
+ return false;
6995
+ }
6996
+ const clinicGroupData = clinicGroupDoc.data();
6997
+ return !!clinicGroupData.autoConfirmAppointments;
6998
+ }
6999
+
7000
+ // src/services/calendar/utils/patient.utils.ts
7001
+ var import_firestore27 = require("firebase/firestore");
7002
+ async function createPatientCalendarEventUtil(db, patientId, eventData, generateId2) {
7003
+ const eventId = generateId2();
7004
+ const eventRef = getPatientCalendarEventDocRef(db, patientId, eventId);
7005
+ const newEvent = {
7006
+ id: eventId,
7007
+ ...eventData,
7008
+ createdAt: (0, import_firestore27.serverTimestamp)(),
7009
+ updatedAt: (0, import_firestore27.serverTimestamp)()
7010
+ };
7011
+ await (0, import_firestore27.setDoc)(eventRef, newEvent);
7012
+ return {
7013
+ ...newEvent,
7014
+ createdAt: import_firestore27.Timestamp.now(),
7015
+ updatedAt: import_firestore27.Timestamp.now()
7016
+ };
7017
+ }
7018
+ async function updatePatientCalendarEventUtil(db, patientId, eventId, updateData) {
7019
+ const eventRef = getPatientCalendarEventDocRef(db, patientId, eventId);
7020
+ const updates = {
7021
+ ...updateData,
7022
+ updatedAt: (0, import_firestore27.serverTimestamp)()
7023
+ };
7024
+ await (0, import_firestore27.updateDoc)(eventRef, updates);
7025
+ const updatedDoc = await (0, import_firestore27.getDoc)(eventRef);
7026
+ if (!updatedDoc.exists()) {
7027
+ throw new Error("Event not found after update");
7028
+ }
7029
+ return updatedDoc.data();
7030
+ }
7031
+
7032
+ // src/services/calendar/utils/practitioner.utils.ts
7033
+ var import_firestore28 = require("firebase/firestore");
7034
+ async function createPractitionerCalendarEventUtil(db, practitionerId, eventData, generateId2) {
7035
+ const eventId = generateId2();
7036
+ const eventRef = getPractitionerCalendarEventDocRef(
7037
+ db,
7038
+ practitionerId,
7039
+ eventId
7040
+ );
7041
+ const newEvent = {
7042
+ id: eventId,
7043
+ ...eventData,
7044
+ createdAt: (0, import_firestore28.serverTimestamp)(),
7045
+ updatedAt: (0, import_firestore28.serverTimestamp)()
7046
+ };
7047
+ await (0, import_firestore28.setDoc)(eventRef, newEvent);
7048
+ return {
7049
+ ...newEvent,
7050
+ createdAt: import_firestore28.Timestamp.now(),
7051
+ updatedAt: import_firestore28.Timestamp.now()
7052
+ };
7053
+ }
7054
+ async function updatePractitionerCalendarEventUtil(db, practitionerId, eventId, updateData) {
7055
+ const eventRef = getPractitionerCalendarEventDocRef(
7056
+ db,
7057
+ practitionerId,
7058
+ eventId
7059
+ );
7060
+ const updates = {
7061
+ ...updateData,
7062
+ updatedAt: (0, import_firestore28.serverTimestamp)()
7063
+ };
7064
+ await (0, import_firestore28.updateDoc)(eventRef, updates);
7065
+ const updatedDoc = await (0, import_firestore28.getDoc)(eventRef);
7066
+ if (!updatedDoc.exists()) {
7067
+ throw new Error("Event not found after update");
7068
+ }
7069
+ return updatedDoc.data();
7070
+ }
7071
+
7072
+ // src/services/calendar/utils/appointment.utils.ts
7073
+ async function createAppointmentUtil(db, clinicId, practitionerId, patientId, eventData, generateId2) {
7074
+ const eventId = generateId2();
7075
+ const autoConfirm = await checkAutoConfirmAppointmentsUtil(db, clinicId);
7076
+ const initialStatus = autoConfirm ? "confirmed" /* CONFIRMED */ : "pending" /* PENDING */;
7077
+ const appointmentData = {
7078
+ ...eventData,
7079
+ clinicBranchId: clinicId,
7080
+ practitionerProfileId: practitionerId,
7081
+ patientProfileId: patientId,
7082
+ eventType: "appointment" /* APPOINTMENT */,
7083
+ status: eventData.status || initialStatus
7084
+ };
7085
+ const clinicPromise = createClinicCalendarEventUtil(
7086
+ db,
7087
+ clinicId,
7088
+ appointmentData,
7089
+ () => eventId
7090
+ // Use the same ID for all calendars
7091
+ );
7092
+ const practitionerPromise = createPractitionerCalendarEventUtil(
7093
+ db,
7094
+ practitionerId,
7095
+ appointmentData,
7096
+ () => eventId
7097
+ // Use the same ID for all calendars
7098
+ );
7099
+ const patientPromise = createPatientCalendarEventUtil(
7100
+ db,
7101
+ patientId,
7102
+ appointmentData,
7103
+ () => eventId
7104
+ // Use the same ID for all calendars
7105
+ );
7106
+ const [clinicEvent] = await Promise.all([
7107
+ clinicPromise,
7108
+ practitionerPromise,
7109
+ patientPromise
7110
+ ]);
7111
+ return clinicEvent;
7112
+ }
7113
+ async function updateAppointmentUtil(db, clinicId, practitionerId, patientId, eventId, updateData) {
7114
+ const clinicPromise = updateClinicCalendarEventUtil(
7115
+ db,
7116
+ clinicId,
7117
+ eventId,
7118
+ updateData
7119
+ );
7120
+ const practitionerPromise = updatePractitionerCalendarEventUtil(
7121
+ db,
7122
+ practitionerId,
7123
+ eventId,
7124
+ updateData
7125
+ );
7126
+ const patientPromise = updatePatientCalendarEventUtil(
7127
+ db,
7128
+ patientId,
7129
+ eventId,
7130
+ updateData
7131
+ );
7132
+ const [clinicEvent] = await Promise.all([
7133
+ clinicPromise,
7134
+ practitionerPromise,
7135
+ patientPromise
7136
+ ]);
7137
+ return clinicEvent;
7138
+ }
7139
+
7140
+ // src/services/calendar/utils/synced-calendar.utils.ts
7141
+ var import_firestore29 = require("firebase/firestore");
7142
+ async function createPractitionerSyncedCalendarUtil(db, practitionerId, calendarData, generateId2) {
7143
+ const calendarId = generateId2();
7144
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
7145
+ db,
7146
+ practitionerId,
7147
+ calendarId
7148
+ );
7149
+ const newCalendar = {
7150
+ id: calendarId,
7151
+ ...calendarData,
7152
+ createdAt: (0, import_firestore29.serverTimestamp)(),
7153
+ updatedAt: (0, import_firestore29.serverTimestamp)()
7154
+ };
7155
+ await (0, import_firestore29.setDoc)(calendarRef, newCalendar);
7156
+ return {
7157
+ ...newCalendar,
7158
+ createdAt: import_firestore29.Timestamp.now(),
7159
+ updatedAt: import_firestore29.Timestamp.now()
7160
+ };
7161
+ }
7162
+ async function createPatientSyncedCalendarUtil(db, patientId, calendarData, generateId2) {
7163
+ const calendarId = generateId2();
7164
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
7165
+ const newCalendar = {
7166
+ id: calendarId,
7167
+ ...calendarData,
7168
+ createdAt: (0, import_firestore29.serverTimestamp)(),
7169
+ updatedAt: (0, import_firestore29.serverTimestamp)()
7170
+ };
7171
+ await (0, import_firestore29.setDoc)(calendarRef, newCalendar);
7172
+ return {
7173
+ ...newCalendar,
7174
+ createdAt: import_firestore29.Timestamp.now(),
7175
+ updatedAt: import_firestore29.Timestamp.now()
7176
+ };
7177
+ }
7178
+ async function createClinicSyncedCalendarUtil(db, clinicId, calendarData, generateId2) {
7179
+ const calendarId = generateId2();
7180
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
7181
+ const newCalendar = {
7182
+ id: calendarId,
7183
+ ...calendarData,
7184
+ createdAt: (0, import_firestore29.serverTimestamp)(),
7185
+ updatedAt: (0, import_firestore29.serverTimestamp)()
7186
+ };
7187
+ await (0, import_firestore29.setDoc)(calendarRef, newCalendar);
7188
+ return {
7189
+ ...newCalendar,
7190
+ createdAt: import_firestore29.Timestamp.now(),
7191
+ updatedAt: import_firestore29.Timestamp.now()
7192
+ };
7193
+ }
7194
+ async function getPractitionerSyncedCalendarUtil(db, practitionerId, calendarId) {
7195
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
7196
+ db,
7197
+ practitionerId,
7198
+ calendarId
7199
+ );
7200
+ const calendarDoc = await (0, import_firestore29.getDoc)(calendarRef);
7201
+ if (!calendarDoc.exists()) {
7202
+ return null;
7203
+ }
7204
+ return calendarDoc.data();
7205
+ }
7206
+ async function getPractitionerSyncedCalendarsUtil(db, practitionerId) {
7207
+ const calendarsRef = (0, import_firestore29.collection)(
7208
+ db,
7209
+ `practitioners/${practitionerId}/${SYNCED_CALENDARS_COLLECTION}`
7210
+ );
7211
+ const q = (0, import_firestore29.query)(calendarsRef, (0, import_firestore29.orderBy)("createdAt", "desc"));
7212
+ const querySnapshot = await (0, import_firestore29.getDocs)(q);
7213
+ return querySnapshot.docs.map((doc20) => doc20.data());
7214
+ }
7215
+ async function getPatientSyncedCalendarUtil(db, patientId, calendarId) {
7216
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
7217
+ const calendarDoc = await (0, import_firestore29.getDoc)(calendarRef);
7218
+ if (!calendarDoc.exists()) {
7219
+ return null;
7220
+ }
7221
+ return calendarDoc.data();
7222
+ }
7223
+ async function getPatientSyncedCalendarsUtil(db, patientId) {
7224
+ const calendarsRef = (0, import_firestore29.collection)(
7225
+ db,
7226
+ `patients/${patientId}/${SYNCED_CALENDARS_COLLECTION}`
7227
+ );
7228
+ const q = (0, import_firestore29.query)(calendarsRef, (0, import_firestore29.orderBy)("createdAt", "desc"));
7229
+ const querySnapshot = await (0, import_firestore29.getDocs)(q);
7230
+ return querySnapshot.docs.map((doc20) => doc20.data());
7231
+ }
7232
+ async function getClinicSyncedCalendarUtil(db, clinicId, calendarId) {
7233
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
7234
+ const calendarDoc = await (0, import_firestore29.getDoc)(calendarRef);
7235
+ if (!calendarDoc.exists()) {
7236
+ return null;
7237
+ }
7238
+ return calendarDoc.data();
7239
+ }
7240
+ async function getClinicSyncedCalendarsUtil(db, clinicId) {
7241
+ const calendarsRef = (0, import_firestore29.collection)(
7242
+ db,
7243
+ `clinics/${clinicId}/${SYNCED_CALENDARS_COLLECTION}`
7244
+ );
7245
+ const q = (0, import_firestore29.query)(calendarsRef, (0, import_firestore29.orderBy)("createdAt", "desc"));
7246
+ const querySnapshot = await (0, import_firestore29.getDocs)(q);
7247
+ return querySnapshot.docs.map((doc20) => doc20.data());
7248
+ }
7249
+ async function updatePractitionerSyncedCalendarUtil(db, practitionerId, calendarId, updateData) {
7250
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
7251
+ db,
7252
+ practitionerId,
7253
+ calendarId
7254
+ );
7255
+ const updates = {
7256
+ ...updateData,
7257
+ updatedAt: (0, import_firestore29.serverTimestamp)()
7258
+ };
7259
+ await (0, import_firestore29.updateDoc)(calendarRef, updates);
7260
+ const updatedDoc = await (0, import_firestore29.getDoc)(calendarRef);
7261
+ if (!updatedDoc.exists()) {
7262
+ throw new Error("Synced calendar not found after update");
7263
+ }
7264
+ return updatedDoc.data();
7265
+ }
7266
+ async function updatePatientSyncedCalendarUtil(db, patientId, calendarId, updateData) {
7267
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
7268
+ const updates = {
7269
+ ...updateData,
7270
+ updatedAt: (0, import_firestore29.serverTimestamp)()
7271
+ };
7272
+ await (0, import_firestore29.updateDoc)(calendarRef, updates);
7273
+ const updatedDoc = await (0, import_firestore29.getDoc)(calendarRef);
7274
+ if (!updatedDoc.exists()) {
7275
+ throw new Error("Synced calendar not found after update");
7276
+ }
7277
+ return updatedDoc.data();
7278
+ }
7279
+ async function updateClinicSyncedCalendarUtil(db, clinicId, calendarId, updateData) {
7280
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
7281
+ const updates = {
7282
+ ...updateData,
7283
+ updatedAt: (0, import_firestore29.serverTimestamp)()
7284
+ };
7285
+ await (0, import_firestore29.updateDoc)(calendarRef, updates);
7286
+ const updatedDoc = await (0, import_firestore29.getDoc)(calendarRef);
7287
+ if (!updatedDoc.exists()) {
7288
+ throw new Error("Synced calendar not found after update");
7289
+ }
7290
+ return updatedDoc.data();
7291
+ }
7292
+ async function deletePractitionerSyncedCalendarUtil(db, practitionerId, calendarId) {
7293
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
7294
+ db,
7295
+ practitionerId,
7296
+ calendarId
7297
+ );
7298
+ await (0, import_firestore29.deleteDoc)(calendarRef);
7299
+ }
7300
+ async function deletePatientSyncedCalendarUtil(db, patientId, calendarId) {
7301
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
7302
+ await (0, import_firestore29.deleteDoc)(calendarRef);
7303
+ }
7304
+ async function deleteClinicSyncedCalendarUtil(db, clinicId, calendarId) {
7305
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
7306
+ await (0, import_firestore29.deleteDoc)(calendarRef);
7307
+ }
7308
+ async function updateLastSyncedTimestampUtil(db, entityType, entityId, calendarId) {
7309
+ const updateData = {
7310
+ lastSyncedAt: import_firestore29.Timestamp.now()
7311
+ };
7312
+ switch (entityType) {
7313
+ case "practitioner":
7314
+ return updatePractitionerSyncedCalendarUtil(
7315
+ db,
7316
+ entityId,
7317
+ calendarId,
7318
+ updateData
7319
+ );
7320
+ case "patient":
7321
+ return updatePatientSyncedCalendarUtil(
7322
+ db,
7323
+ entityId,
7324
+ calendarId,
7325
+ updateData
7326
+ );
7327
+ case "clinic":
7328
+ return updateClinicSyncedCalendarUtil(
7329
+ db,
7330
+ entityId,
7331
+ calendarId,
7332
+ updateData
7333
+ );
7334
+ default:
7335
+ throw new Error(`Invalid entity type: ${entityType}`);
7336
+ }
7337
+ }
7338
+
7339
+ // src/services/calendar/utils/google-calendar.utils.ts
7340
+ var import_firestore30 = require("firebase/firestore");
7341
+ var GOOGLE_CALENDAR_API_URL = "https://www.googleapis.com/calendar/v3";
7342
+ var GOOGLE_OAUTH_URL = "https://oauth2.googleapis.com/token";
7343
+ var CLIENT_ID = "your-client-id";
7344
+ var CLIENT_SECRET = "your-client-secret";
7345
+ var REDIRECT_URI = "your-redirect-uri";
7346
+ async function makeRequest(method, url, headers, data, params) {
7347
+ const queryParams = params ? "?" + Object.entries(params).map(
7348
+ ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
7349
+ ).join("&") : "";
7350
+ const finalUrl = url + queryParams;
7351
+ const options = {
7352
+ method,
7353
+ headers,
7354
+ body: data ? JSON.stringify(data) : void 0
7355
+ };
7356
+ const response = await fetch(finalUrl, options);
7357
+ if (!response.ok) {
7358
+ const error = new Error(
7359
+ `Request failed with status ${response.status}`
7360
+ );
7361
+ error.response = response;
7362
+ throw error;
7363
+ }
7364
+ return response.json();
7365
+ }
7366
+ async function authenticateWithGoogleCalendarUtil(authCode) {
7367
+ try {
7368
+ const data = {
7369
+ code: authCode,
7370
+ client_id: CLIENT_ID,
7371
+ client_secret: CLIENT_SECRET,
7372
+ redirect_uri: REDIRECT_URI,
7373
+ grant_type: "authorization_code"
7374
+ };
7375
+ const response = await makeRequest(
7376
+ "post",
7377
+ GOOGLE_OAUTH_URL,
7378
+ { "Content-Type": "application/json" },
7379
+ data
7380
+ );
7381
+ return {
7382
+ accessToken: response.access_token,
7383
+ refreshToken: response.refresh_token,
7384
+ expiresIn: response.expires_in
7385
+ };
7386
+ } catch (error) {
7387
+ const apiError = error;
7388
+ console.error(
7389
+ "Error authenticating with Google Calendar:",
7390
+ apiError.message || "Unknown error"
7391
+ );
7392
+ throw new Error(
7393
+ `Failed to authenticate with Google Calendar: ${apiError.message || "Unknown error"}`
7394
+ );
7395
+ }
7396
+ }
7397
+ async function refreshGoogleCalendarTokenUtil(refreshToken) {
7398
+ try {
7399
+ const data = {
7400
+ refresh_token: refreshToken,
7401
+ client_id: CLIENT_ID,
7402
+ client_secret: CLIENT_SECRET,
7403
+ grant_type: "refresh_token"
7404
+ };
7405
+ const response = await makeRequest(
7406
+ "post",
7407
+ GOOGLE_OAUTH_URL,
7408
+ { "Content-Type": "application/json" },
7409
+ data
7410
+ );
7411
+ return {
7412
+ accessToken: response.access_token,
7413
+ expiresIn: response.expires_in
7414
+ };
7415
+ } catch (error) {
7416
+ const apiError = error;
7417
+ console.error(
7418
+ "Error refreshing Google Calendar token:",
7419
+ apiError.message || "Unknown error"
7420
+ );
7421
+ throw new Error(
7422
+ `Failed to refresh Google Calendar token: ${apiError.message || "Unknown error"}`
7423
+ );
7424
+ }
7425
+ }
7426
+ async function listGoogleCalendarsUtil(accessToken) {
7427
+ try {
7428
+ const response = await makeRequest(
7429
+ "get",
7430
+ `${GOOGLE_CALENDAR_API_URL}/users/me/calendarList`,
7431
+ { Authorization: `Bearer ${accessToken}` }
7432
+ );
7433
+ return response.items.map((calendar) => ({
7434
+ id: calendar.id,
7435
+ name: calendar.summary
7436
+ }));
7437
+ } catch (error) {
7438
+ const apiError = error;
7439
+ console.error(
7440
+ "Error listing Google Calendars:",
7441
+ apiError.message || "Unknown error"
7442
+ );
7443
+ throw new Error(
7444
+ `Failed to list Google Calendars: ${apiError.message || "Unknown error"}`
7445
+ );
7446
+ }
7447
+ }
7448
+ async function ensureValidToken(db, entityType, entityId, syncedCalendar) {
7449
+ const expiryTime = syncedCalendar.tokenExpiry.toDate();
7450
+ const now = /* @__PURE__ */ new Date();
7451
+ const fiveMinutesFromNow = new Date(now.getTime() + 5 * 60 * 1e3);
7452
+ if (expiryTime < fiveMinutesFromNow) {
7453
+ const { accessToken, expiresIn } = await refreshGoogleCalendarTokenUtil(
7454
+ syncedCalendar.refreshToken
7455
+ );
7456
+ const tokenExpiry = /* @__PURE__ */ new Date();
7457
+ tokenExpiry.setSeconds(tokenExpiry.getSeconds() + expiresIn);
7458
+ const updateData = {
7459
+ accessToken,
7460
+ tokenExpiry: import_firestore30.Timestamp.fromDate(tokenExpiry)
7461
+ };
7462
+ switch (entityType) {
7463
+ case "practitioner":
7464
+ await updatePractitionerSyncedCalendarUtil(
7465
+ db,
7466
+ entityId,
7467
+ syncedCalendar.id,
7468
+ updateData
7469
+ );
7470
+ break;
7471
+ case "patient":
7472
+ await updatePatientSyncedCalendarUtil(
7473
+ db,
7474
+ entityId,
7475
+ syncedCalendar.id,
7476
+ updateData
7477
+ );
7478
+ break;
7479
+ case "clinic":
7480
+ await updateClinicSyncedCalendarUtil(
7481
+ db,
7482
+ entityId,
7483
+ syncedCalendar.id,
7484
+ updateData
7485
+ );
7486
+ break;
7487
+ }
7488
+ return accessToken;
7489
+ }
7490
+ return syncedCalendar.accessToken;
7491
+ }
7492
+ async function syncEventsToGoogleCalendarUtil(db, entityType, entityId, syncedCalendar, events, existingSyncId) {
7493
+ var _a, _b;
7494
+ try {
7495
+ const { accessToken } = await refreshGoogleCalendarTokenUtil(
7496
+ syncedCalendar.refreshToken
7497
+ );
7498
+ let syncedCount = 0;
7499
+ const errors = [];
7500
+ const eventIds = [];
7501
+ for (const event of events) {
7502
+ try {
7503
+ if (event.syncStatus === "external" /* EXTERNAL */) {
7504
+ continue;
7505
+ }
7506
+ if (entityType === "practitioner" && event.status !== "confirmed" /* CONFIRMED */) {
7507
+ continue;
7508
+ }
7509
+ if (entityType === "patient" && (event.status === "canceled" /* CANCELED */ || event.status === "rejected" /* REJECTED */)) {
7510
+ continue;
7511
+ }
7512
+ if (entityType === "clinic") {
7513
+ continue;
7514
+ }
7515
+ const googleEvent = convertCalendarEventToGoogleEventUtil(event);
7516
+ const headers = {
7517
+ Authorization: `Bearer ${accessToken}`,
7518
+ "Content-Type": "application/json"
7519
+ };
7520
+ let responseId = "";
7521
+ if (existingSyncId) {
7522
+ const response = await makeRequest(
7523
+ "put",
7524
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${existingSyncId}`,
7525
+ headers,
7526
+ googleEvent
7527
+ );
7528
+ responseId = response.id;
7529
+ } else {
7530
+ const existingSync = (_a = event.syncedCalendarEventId) == null ? void 0 : _a.find(
7531
+ (sync) => sync.syncedCalendarProvider === "google" /* GOOGLE */ && // We should check if this is the same calendar we're syncing with, but that information isn't stored
7532
+ // For now, we'll just use the first Google Calendar sync ID
7533
+ sync.syncedCalendarProvider === syncedCalendar.provider
7534
+ );
7535
+ if (existingSync) {
7536
+ const response = await makeRequest(
7537
+ "put",
7538
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${existingSync.eventId}`,
7539
+ headers,
7540
+ googleEvent
7541
+ );
7542
+ responseId = response.id;
7543
+ } else {
7544
+ const response = await makeRequest(
7545
+ "post",
7546
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events`,
7547
+ headers,
7548
+ googleEvent
7549
+ );
7550
+ responseId = response.id;
7551
+ }
7552
+ }
7553
+ if (responseId) {
7554
+ eventIds.push(responseId);
7555
+ syncedCount++;
7556
+ }
7557
+ } catch (error) {
7558
+ const apiError = error;
7559
+ errors.push({
7560
+ eventId: event.id,
7561
+ error: apiError.message || "Unknown error",
7562
+ status: (_b = apiError.response) == null ? void 0 : _b.status
7563
+ });
7564
+ }
7565
+ }
7566
+ await updateLastSyncedTimestampUtil(
7567
+ db,
7568
+ entityType,
7569
+ entityId,
7570
+ syncedCalendar.id
7571
+ );
7572
+ return {
7573
+ success: errors.length === 0,
7574
+ syncedEvents: syncedCount,
7575
+ errors,
7576
+ eventIds
7577
+ };
7578
+ } catch (error) {
7579
+ console.error("Error syncing with Google Calendar:", error);
7580
+ return {
7581
+ success: false,
7582
+ syncedEvents: 0,
7583
+ errors: [{ error: error.message || "Unknown error" }],
7584
+ eventIds: []
7585
+ };
7586
+ }
7587
+ }
7588
+ async function fetchEventsFromGoogleCalendarUtil(db, entityType, entityId, syncedCalendar, startDate, endDate) {
7589
+ try {
7590
+ const accessToken = await ensureValidToken(
7591
+ db,
7592
+ entityType,
7593
+ entityId,
7594
+ syncedCalendar
7595
+ );
7596
+ const timeMin = startDate.toISOString();
7597
+ const timeMax = endDate.toISOString();
7598
+ const response = await makeRequest(
7599
+ "get",
7600
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events`,
7601
+ { Authorization: `Bearer ${accessToken}` },
7602
+ void 0,
7603
+ {
7604
+ timeMin,
7605
+ timeMax,
7606
+ singleEvents: "true",
7607
+ orderBy: "startTime"
7608
+ }
7609
+ );
7610
+ await updateLastSyncedTimestampUtil(
7611
+ db,
7612
+ entityType,
7613
+ entityId,
7614
+ syncedCalendar.id
7615
+ );
7616
+ return response.items;
7617
+ } catch (error) {
7618
+ const apiError = error;
7619
+ console.error(
7620
+ "Error fetching events from Google Calendar:",
7621
+ apiError.message || "Unknown error"
7622
+ );
7623
+ throw new Error(
7624
+ `Failed to fetch events from Google Calendar: ${apiError.message || "Unknown error"}`
7625
+ );
7626
+ }
7627
+ }
7628
+ function convertGoogleEventToCalendarEventUtil(googleEvent, entityId, entityType) {
7629
+ const start = googleEvent.start.dateTime ? new Date(googleEvent.start.dateTime) : new Date(googleEvent.start.date);
7630
+ const end = googleEvent.end.dateTime ? new Date(googleEvent.end.dateTime) : new Date(googleEvent.end.date);
7631
+ const calendarEvent = {
7632
+ eventName: googleEvent.summary || "External Event",
7633
+ eventLocation: googleEvent.location,
7634
+ eventTime: {
7635
+ start: import_firestore30.Timestamp.fromDate(start),
7636
+ end: import_firestore30.Timestamp.fromDate(end)
7637
+ },
7638
+ description: googleEvent.description || "",
7639
+ // External events are always set as CONFIRMED - status updates will happen externally
7640
+ status: "confirmed" /* CONFIRMED */,
7641
+ // All external events are marked as EXTERNAL to indicate they originated outside our system
7642
+ syncStatus: "external" /* EXTERNAL */,
7643
+ // All external events are treated as BLOCKING events
7644
+ eventType: "blocking" /* BLOCKING */,
7645
+ // Store the original Google Calendar event ID
7646
+ syncedCalendarEventId: [
7647
+ {
7648
+ eventId: googleEvent.id,
7649
+ syncedCalendarProvider: "google" /* GOOGLE */,
7650
+ syncedAt: import_firestore30.Timestamp.now()
7651
+ }
7652
+ ]
7653
+ };
7654
+ switch (entityType) {
7655
+ case "practitioner":
7656
+ calendarEvent.practitionerProfileId = entityId;
7657
+ break;
7658
+ case "patient":
7659
+ calendarEvent.patientProfileId = entityId;
7660
+ break;
7661
+ case "clinic":
7662
+ calendarEvent.clinicBranchId = entityId;
7663
+ break;
7664
+ }
7665
+ return calendarEvent;
7666
+ }
7667
+ function convertCalendarEventToGoogleEventUtil(calendarEvent) {
7668
+ const googleEvent = {
7669
+ summary: calendarEvent.eventName,
7670
+ location: calendarEvent.eventLocation,
7671
+ description: calendarEvent.description,
7672
+ start: {
7673
+ dateTime: calendarEvent.eventTime.start.toDate().toISOString(),
7674
+ timeZone: "UTC"
7675
+ },
7676
+ end: {
7677
+ dateTime: calendarEvent.eventTime.end.toDate().toISOString(),
7678
+ timeZone: "UTC"
7679
+ },
7680
+ // Add reminders
7681
+ reminders: {
7682
+ useDefault: false,
7683
+ overrides: [
7684
+ { method: "email", minutes: 24 * 60 },
7685
+ // 1 day before
7686
+ { method: "popup", minutes: 30 }
7687
+ // 30 minutes before
7688
+ ]
7689
+ }
7690
+ };
7691
+ switch (calendarEvent.status) {
7692
+ case "confirmed" /* CONFIRMED */:
7693
+ googleEvent.status = "confirmed";
7694
+ break;
7695
+ case "canceled" /* CANCELED */:
7696
+ googleEvent.status = "cancelled";
7697
+ break;
7698
+ case "pending" /* PENDING */:
7699
+ googleEvent.status = "tentative";
7700
+ break;
7701
+ default:
7702
+ googleEvent.status = "confirmed";
7703
+ }
7704
+ if (calendarEvent.eventType === "appointment" /* APPOINTMENT */) {
7705
+ googleEvent.attendees = [];
7706
+ if (calendarEvent.practitionerProfileId) {
7707
+ googleEvent.attendees.push({
7708
+ email: "practitioner@example.com",
7709
+ // This would be fetched from the practitioner profile
7710
+ displayName: "Dr. Practitioner",
7711
+ // This would be fetched from the practitioner profile
7712
+ responseStatus: "accepted"
7713
+ });
7714
+ }
7715
+ if (calendarEvent.patientProfileId) {
7716
+ googleEvent.attendees.push({
7717
+ email: "patient@example.com",
7718
+ // This would be fetched from the patient profile
7719
+ displayName: "Patient",
7720
+ // This would be fetched from the patient profile
7721
+ responseStatus: "needsAction"
7722
+ });
7723
+ }
7724
+ }
7725
+ return googleEvent;
7726
+ }
7727
+ async function deleteGoogleCalendarEventUtil(db, entityType, entityId, syncedCalendar, eventId) {
7728
+ try {
7729
+ const accessToken = await ensureValidToken(
7730
+ db,
7731
+ entityType,
7732
+ entityId,
7733
+ syncedCalendar
7734
+ );
7735
+ await makeRequest(
7736
+ "delete",
7737
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${eventId}`,
7738
+ { Authorization: `Bearer ${accessToken}` }
7739
+ );
7740
+ return true;
7741
+ } catch (error) {
7742
+ const apiError = error;
7743
+ console.error(
7744
+ "Error deleting event from Google Calendar:",
7745
+ apiError.message || "Unknown error"
7746
+ );
7747
+ throw new Error(
7748
+ `Failed to delete event from Google Calendar: ${apiError.message || "Unknown error"}`
7749
+ );
7750
+ }
7751
+ }
7752
+ function getGoogleCalendarOAuthUrlUtil(scopes = ["https://www.googleapis.com/auth/calendar"]) {
7753
+ const scopeString = encodeURIComponent(scopes.join(" "));
7754
+ return `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(
7755
+ REDIRECT_URI
7756
+ )}&response_type=code&scope=${scopeString}&access_type=offline&prompt=consent`;
7757
+ }
7758
+
7759
+ // src/services/calendar/synced-calendars.service.ts
7760
+ var SyncedCalendarsService = class extends BaseService {
7761
+ /**
7762
+ * Creates a new SyncedCalendarsService instance
7763
+ * @param db - Firestore instance
7764
+ * @param auth - Firebase Auth instance
7765
+ * @param app - Firebase App instance
7766
+ */
7767
+ constructor(db, auth, app) {
7768
+ super(db, auth, app);
7769
+ }
7770
+ // ===== Practitioner Synced Calendars =====
7771
+ /**
7772
+ * Creates a synced calendar for a practitioner
7773
+ * @param practitionerId - ID of the practitioner
7774
+ * @param calendarData - Synced calendar data
7775
+ * @returns Created synced calendar
7776
+ */
7777
+ async createPractitionerSyncedCalendar(practitionerId, calendarData) {
7778
+ return createPractitionerSyncedCalendarUtil(
7779
+ this.db,
7780
+ practitionerId,
7781
+ calendarData,
7782
+ this.generateId.bind(this)
7783
+ );
7784
+ }
7785
+ /**
7786
+ * Gets a synced calendar for a practitioner
7787
+ * @param practitionerId - ID of the practitioner
7788
+ * @param calendarId - ID of the synced calendar
7789
+ * @returns Synced calendar or null if not found
7790
+ */
7791
+ async getPractitionerSyncedCalendar(practitionerId, calendarId) {
7792
+ return getPractitionerSyncedCalendarUtil(
7793
+ this.db,
7794
+ practitionerId,
7795
+ calendarId
7796
+ );
7797
+ }
7798
+ /**
7799
+ * Gets all synced calendars for a practitioner
7800
+ * @param practitionerId - ID of the practitioner
7801
+ * @returns Array of synced calendars
7802
+ */
7803
+ async getPractitionerSyncedCalendars(practitionerId) {
7804
+ return getPractitionerSyncedCalendarsUtil(this.db, practitionerId);
7805
+ }
7806
+ /**
7807
+ * Updates a synced calendar for a practitioner
7808
+ * @param practitionerId - ID of the practitioner
7809
+ * @param calendarId - ID of the synced calendar
7810
+ * @param updateData - Data to update
7811
+ * @returns Updated synced calendar
7812
+ */
7813
+ async updatePractitionerSyncedCalendar(practitionerId, calendarId, updateData) {
7814
+ return updatePractitionerSyncedCalendarUtil(
7815
+ this.db,
7816
+ practitionerId,
7817
+ calendarId,
7818
+ updateData
7819
+ );
7820
+ }
7821
+ /**
7822
+ * Deletes a synced calendar for a practitioner
7823
+ * @param practitionerId - ID of the practitioner
7824
+ * @param calendarId - ID of the synced calendar
7825
+ */
7826
+ async deletePractitionerSyncedCalendar(practitionerId, calendarId) {
7827
+ return deletePractitionerSyncedCalendarUtil(
7828
+ this.db,
7829
+ practitionerId,
7830
+ calendarId
7831
+ );
7832
+ }
7833
+ // ===== Patient Synced Calendars =====
7834
+ /**
7835
+ * Creates a synced calendar for a patient
7836
+ * @param patientId - ID of the patient
7837
+ * @param calendarData - Synced calendar data
7838
+ * @returns Created synced calendar
7839
+ */
7840
+ async createPatientSyncedCalendar(patientId, calendarData) {
7841
+ return createPatientSyncedCalendarUtil(
7842
+ this.db,
7843
+ patientId,
7844
+ calendarData,
7845
+ this.generateId.bind(this)
7846
+ );
7847
+ }
7848
+ /**
7849
+ * Gets a synced calendar for a patient
7850
+ * @param patientId - ID of the patient
7851
+ * @param calendarId - ID of the synced calendar
7852
+ * @returns Synced calendar or null if not found
7853
+ */
7854
+ async getPatientSyncedCalendar(patientId, calendarId) {
7855
+ return getPatientSyncedCalendarUtil(this.db, patientId, calendarId);
7856
+ }
7857
+ /**
7858
+ * Gets all synced calendars for a patient
7859
+ * @param patientId - ID of the patient
7860
+ * @returns Array of synced calendars
7861
+ */
7862
+ async getPatientSyncedCalendars(patientId) {
7863
+ return getPatientSyncedCalendarsUtil(this.db, patientId);
7864
+ }
7865
+ /**
7866
+ * Updates a synced calendar for a patient
7867
+ * @param patientId - ID of the patient
7868
+ * @param calendarId - ID of the synced calendar
7869
+ * @param updateData - Data to update
7870
+ * @returns Updated synced calendar
7871
+ */
7872
+ async updatePatientSyncedCalendar(patientId, calendarId, updateData) {
7873
+ return updatePatientSyncedCalendarUtil(
7874
+ this.db,
7875
+ patientId,
7876
+ calendarId,
7877
+ updateData
7878
+ );
7879
+ }
7880
+ /**
7881
+ * Deletes a synced calendar for a patient
7882
+ * @param patientId - ID of the patient
7883
+ * @param calendarId - ID of the synced calendar
7884
+ */
7885
+ async deletePatientSyncedCalendar(patientId, calendarId) {
7886
+ return deletePatientSyncedCalendarUtil(this.db, patientId, calendarId);
7887
+ }
7888
+ // ===== Clinic Synced Calendars =====
7889
+ /**
7890
+ * Creates a synced calendar for a clinic
7891
+ * @param clinicId - ID of the clinic
7892
+ * @param calendarData - Synced calendar data
7893
+ * @returns Created synced calendar
7894
+ */
7895
+ async createClinicSyncedCalendar(clinicId, calendarData) {
7896
+ return createClinicSyncedCalendarUtil(
7897
+ this.db,
7898
+ clinicId,
7899
+ calendarData,
7900
+ this.generateId.bind(this)
7901
+ );
7902
+ }
7903
+ /**
7904
+ * Gets a synced calendar for a clinic
7905
+ * @param clinicId - ID of the clinic
7906
+ * @param calendarId - ID of the synced calendar
7907
+ * @returns Synced calendar or null if not found
7908
+ */
7909
+ async getClinicSyncedCalendar(clinicId, calendarId) {
7910
+ return getClinicSyncedCalendarUtil(this.db, clinicId, calendarId);
7911
+ }
7912
+ /**
7913
+ * Gets all synced calendars for a clinic
7914
+ * @param clinicId - ID of the clinic
7915
+ * @returns Array of synced calendars
7916
+ */
7917
+ async getClinicSyncedCalendars(clinicId) {
7918
+ return getClinicSyncedCalendarsUtil(this.db, clinicId);
7919
+ }
7920
+ /**
7921
+ * Updates a synced calendar for a clinic
7922
+ * @param clinicId - ID of the clinic
7923
+ * @param calendarId - ID of the synced calendar
7924
+ * @param updateData - Data to update
7925
+ * @returns Updated synced calendar
7926
+ */
7927
+ async updateClinicSyncedCalendar(clinicId, calendarId, updateData) {
7928
+ return updateClinicSyncedCalendarUtil(
7929
+ this.db,
7930
+ clinicId,
7931
+ calendarId,
7932
+ updateData
7933
+ );
7934
+ }
7935
+ /**
7936
+ * Deletes a synced calendar for a clinic
7937
+ * @param clinicId - ID of the clinic
7938
+ * @param calendarId - ID of the synced calendar
7939
+ */
7940
+ async deleteClinicSyncedCalendar(clinicId, calendarId) {
7941
+ return deleteClinicSyncedCalendarUtil(this.db, clinicId, calendarId);
7942
+ }
7943
+ // ===== Google Calendar Integration =====
7944
+ /**
7945
+ * Gets the OAuth URL for Google Calendar
7946
+ * @param scopes - OAuth scopes to request
7947
+ * @returns OAuth URL
7948
+ */
7949
+ getGoogleCalendarOAuthUrl(scopes = ["https://www.googleapis.com/auth/calendar"]) {
7950
+ return getGoogleCalendarOAuthUrlUtil(scopes);
7951
+ }
7952
+ /**
7953
+ * Authenticates with Google Calendar using an authorization code
7954
+ * @param authCode - Authorization code from Google OAuth
7955
+ * @returns Access token, refresh token, and expiration time
7956
+ */
7957
+ async authenticateWithGoogleCalendar(authCode) {
7958
+ return authenticateWithGoogleCalendarUtil(authCode);
7959
+ }
7960
+ /**
7961
+ * Lists available Google Calendars for a user
7962
+ * @param accessToken - Google API access token
7963
+ * @returns List of available calendars
7964
+ */
7965
+ async listGoogleCalendars(accessToken) {
7966
+ return listGoogleCalendarsUtil(accessToken);
7967
+ }
7968
+ /**
7969
+ * Syncs events from our system to Google Calendar for a practitioner
7970
+ * @param practitionerId - ID of the practitioner
7971
+ * @param calendarId - ID of the synced calendar
7972
+ * @param events - Events to sync
7973
+ * @param existingSyncId - Optional existing sync ID for updating an event
7974
+ * @returns Result of the sync operation
7975
+ */
7976
+ async syncPractitionerEventsToGoogleCalendar(practitionerId, calendarId, events, existingSyncId) {
7977
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
7978
+ practitionerId,
7979
+ calendarId
7980
+ );
7981
+ if (!syncedCalendar) {
7982
+ throw new Error("Synced calendar not found");
7983
+ }
7984
+ return syncEventsToGoogleCalendarUtil(
7985
+ this.db,
7986
+ "practitioner",
7987
+ practitionerId,
7988
+ syncedCalendar,
7989
+ events,
7990
+ existingSyncId
7991
+ );
7992
+ }
7993
+ /**
7994
+ * Syncs events from our system to Google Calendar for a patient
7995
+ * @param patientId - ID of the patient
7996
+ * @param calendarId - ID of the synced calendar
7997
+ * @param events - Events to sync
7998
+ * @param existingSyncId - Optional existing sync ID for updating an event
7999
+ * @returns Result of the sync operation
8000
+ */
8001
+ async syncPatientEventsToGoogleCalendar(patientId, calendarId, events, existingSyncId) {
8002
+ const syncedCalendar = await this.getPatientSyncedCalendar(
8003
+ patientId,
8004
+ calendarId
8005
+ );
8006
+ if (!syncedCalendar) {
8007
+ throw new Error("Synced calendar not found");
8008
+ }
8009
+ return syncEventsToGoogleCalendarUtil(
8010
+ this.db,
8011
+ "patient",
8012
+ patientId,
8013
+ syncedCalendar,
8014
+ events,
8015
+ existingSyncId
8016
+ );
8017
+ }
8018
+ /**
8019
+ * Syncs events from our system to Google Calendar for a clinic
8020
+ * @param clinicId - ID of the clinic
8021
+ * @param calendarId - ID of the synced calendar
8022
+ * @param events - Events to sync
8023
+ * @returns Result of the sync operation
8024
+ */
8025
+ async syncClinicEventsToGoogleCalendar(clinicId, calendarId, events) {
8026
+ const syncedCalendar = await this.getClinicSyncedCalendar(
8027
+ clinicId,
8028
+ calendarId
8029
+ );
8030
+ if (!syncedCalendar) {
8031
+ throw new Error("Synced calendar not found");
8032
+ }
8033
+ return syncEventsToGoogleCalendarUtil(
8034
+ this.db,
8035
+ "clinic",
8036
+ clinicId,
8037
+ syncedCalendar,
8038
+ events
8039
+ );
8040
+ }
8041
+ /**
8042
+ * Fetches events from Google Calendar for a practitioner
8043
+ * @param practitionerId - ID of the practitioner
8044
+ * @param calendarId - ID of the synced calendar
8045
+ * @param startDate - Start date for fetching events
8046
+ * @param endDate - End date for fetching events
8047
+ * @returns Events fetched from Google Calendar
8048
+ */
8049
+ async fetchEventsFromPractitionerGoogleCalendar(practitionerId, calendarId, startDate, endDate) {
8050
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
8051
+ practitionerId,
8052
+ calendarId
8053
+ );
8054
+ if (!syncedCalendar) {
8055
+ throw new Error("Synced calendar not found");
8056
+ }
8057
+ return fetchEventsFromGoogleCalendarUtil(
8058
+ this.db,
8059
+ "practitioner",
8060
+ practitionerId,
8061
+ syncedCalendar,
8062
+ startDate,
8063
+ endDate
8064
+ );
8065
+ }
8066
+ /**
8067
+ * Fetches events from Google Calendar for a patient
8068
+ * @param patientId - ID of the patient
8069
+ * @param calendarId - ID of the synced calendar
8070
+ * @param startDate - Start date for fetching events
8071
+ * @param endDate - End date for fetching events
8072
+ * @returns Events fetched from Google Calendar
8073
+ */
8074
+ async fetchEventsFromPatientGoogleCalendar(patientId, calendarId, startDate, endDate) {
8075
+ const syncedCalendar = await this.getPatientSyncedCalendar(
8076
+ patientId,
8077
+ calendarId
8078
+ );
8079
+ if (!syncedCalendar) {
8080
+ throw new Error("Synced calendar not found");
8081
+ }
8082
+ return fetchEventsFromGoogleCalendarUtil(
8083
+ this.db,
8084
+ "patient",
8085
+ patientId,
8086
+ syncedCalendar,
8087
+ startDate,
8088
+ endDate
8089
+ );
8090
+ }
8091
+ /**
8092
+ * Fetches events from Google Calendar for a clinic
8093
+ * @param clinicId - ID of the clinic
8094
+ * @param calendarId - ID of the synced calendar
8095
+ * @param startDate - Start date for fetching events
8096
+ * @param endDate - End date for fetching events
8097
+ * @returns Events fetched from Google Calendar
8098
+ */
8099
+ async fetchEventsFromClinicGoogleCalendar(clinicId, calendarId, startDate, endDate) {
8100
+ const syncedCalendar = await this.getClinicSyncedCalendar(
8101
+ clinicId,
8102
+ calendarId
8103
+ );
8104
+ if (!syncedCalendar) {
8105
+ throw new Error("Synced calendar not found");
8106
+ }
8107
+ return fetchEventsFromGoogleCalendarUtil(
8108
+ this.db,
8109
+ "clinic",
8110
+ clinicId,
8111
+ syncedCalendar,
8112
+ startDate,
8113
+ endDate
8114
+ );
8115
+ }
8116
+ /**
8117
+ * Deletes an event from Google Calendar for a practitioner
8118
+ * @param practitionerId - ID of the practitioner
8119
+ * @param calendarId - ID of the synced calendar
8120
+ * @param eventId - ID of the event in Google Calendar
8121
+ * @returns Success status
8122
+ */
8123
+ async deletePractitionerGoogleCalendarEvent(practitionerId, calendarId, eventId) {
8124
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
8125
+ practitionerId,
8126
+ calendarId
8127
+ );
8128
+ if (!syncedCalendar) {
8129
+ throw new Error("Synced calendar not found");
8130
+ }
8131
+ return deleteGoogleCalendarEventUtil(
8132
+ this.db,
8133
+ "practitioner",
8134
+ practitionerId,
8135
+ syncedCalendar,
8136
+ eventId
8137
+ );
8138
+ }
8139
+ /**
8140
+ * Deletes an event from Google Calendar for a patient
8141
+ * @param patientId - ID of the patient
8142
+ * @param calendarId - ID of the synced calendar
8143
+ * @param eventId - ID of the event in Google Calendar
8144
+ * @returns Success status
8145
+ */
8146
+ async deletePatientGoogleCalendarEvent(patientId, calendarId, eventId) {
8147
+ const syncedCalendar = await this.getPatientSyncedCalendar(
8148
+ patientId,
8149
+ calendarId
8150
+ );
8151
+ if (!syncedCalendar) {
8152
+ throw new Error("Synced calendar not found");
8153
+ }
8154
+ return deleteGoogleCalendarEventUtil(
8155
+ this.db,
8156
+ "patient",
8157
+ patientId,
8158
+ syncedCalendar,
8159
+ eventId
8160
+ );
8161
+ }
8162
+ /**
8163
+ * Deletes an event from Google Calendar for a clinic
8164
+ * @param clinicId - ID of the clinic
8165
+ * @param calendarId - ID of the synced calendar
8166
+ * @param eventId - ID of the event in Google Calendar
8167
+ * @returns Success status
8168
+ */
8169
+ async deleteClinicGoogleCalendarEvent(clinicId, calendarId, eventId) {
8170
+ const syncedCalendar = await this.getClinicSyncedCalendar(
8171
+ clinicId,
8172
+ calendarId
8173
+ );
8174
+ if (!syncedCalendar) {
8175
+ throw new Error("Synced calendar not found");
8176
+ }
8177
+ return deleteGoogleCalendarEventUtil(
8178
+ this.db,
8179
+ "clinic",
8180
+ clinicId,
8181
+ syncedCalendar,
8182
+ eventId
8183
+ );
8184
+ }
8185
+ /**
8186
+ * Converts Google Calendar events to our system's format for a practitioner
8187
+ * @param practitionerId - ID of the practitioner
8188
+ * @param googleEvents - Google Calendar events
8189
+ * @returns Converted calendar events
8190
+ */
8191
+ convertGoogleEventsToPractitionerEvents(practitionerId, googleEvents) {
8192
+ return googleEvents.map(
8193
+ (event) => convertGoogleEventToCalendarEventUtil(
8194
+ event,
8195
+ practitionerId,
8196
+ "practitioner"
8197
+ )
8198
+ );
8199
+ }
8200
+ /**
8201
+ * Converts Google Calendar events to our system's format for a patient
8202
+ * @param patientId - ID of the patient
8203
+ * @param googleEvents - Google Calendar events
8204
+ * @returns Converted calendar events
8205
+ */
8206
+ convertGoogleEventsToPatientEvents(patientId, googleEvents) {
8207
+ return googleEvents.map(
8208
+ (event) => convertGoogleEventToCalendarEventUtil(event, patientId, "patient")
8209
+ );
8210
+ }
8211
+ /**
8212
+ * Converts Google Calendar events to our system's format for a clinic
8213
+ * @param clinicId - ID of the clinic
8214
+ * @param googleEvents - Google Calendar events
8215
+ * @returns Converted calendar events
8216
+ */
8217
+ convertGoogleEventsToClinicEvents(clinicId, googleEvents) {
8218
+ return googleEvents.map(
8219
+ (event) => convertGoogleEventToCalendarEventUtil(event, clinicId, "clinic")
8220
+ );
8221
+ }
8222
+ /**
8223
+ * Fetches a single event from Google Calendar for a practitioner
8224
+ * @param practitionerId - ID of the practitioner
8225
+ * @param calendarId - ID of the synced calendar
8226
+ * @param eventId - ID of the event in Google Calendar
8227
+ * @returns The event data or null if not found
8228
+ */
8229
+ async fetchEventFromPractitionerGoogleCalendar(practitionerId, calendarId, eventId) {
8230
+ var _a;
8231
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
8232
+ practitionerId,
8233
+ calendarId
8234
+ );
8235
+ if (!syncedCalendar) {
8236
+ throw new Error("Synced calendar not found");
8237
+ }
8238
+ try {
8239
+ const { accessToken } = await refreshGoogleCalendarTokenUtil(
8240
+ syncedCalendar.refreshToken
8241
+ );
8242
+ const response = await makeRequest(
8243
+ "get",
8244
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${eventId}`,
8245
+ { Authorization: `Bearer ${accessToken}` }
8246
+ );
8247
+ await updateLastSyncedTimestampUtil(
8248
+ this.db,
8249
+ "practitioner",
8250
+ practitionerId,
8251
+ syncedCalendar.id
8252
+ );
8253
+ return response;
8254
+ } catch (error) {
8255
+ if (((_a = error.response) == null ? void 0 : _a.status) === 404) {
8256
+ return null;
8257
+ }
8258
+ console.error(
8259
+ `Error fetching event from Google Calendar: ${error.message}`
8260
+ );
8261
+ throw error;
8262
+ }
8263
+ }
8264
+ };
8265
+
8266
+ // src/services/calendar/calendar-refactored.service.ts
8267
+ var MIN_APPOINTMENT_DURATION2 = 15;
8268
+ var CalendarServiceV2 = class extends BaseService {
8269
+ /**
8270
+ * Creates a new CalendarService instance
8271
+ * @param db - Firestore instance
8272
+ * @param auth - Firebase Auth instance
8273
+ * @param app - Firebase App instance
8274
+ */
8275
+ constructor(db, auth, app) {
8276
+ super(db, auth, app);
8277
+ this.syncedCalendarsService = new SyncedCalendarsService(db, auth, app);
8278
+ }
8279
+ // #region Public API Methods
8280
+ /**
8281
+ * Creates a new appointment with proper validation and scheduling rules
8282
+ * @param params - Appointment creation parameters
8283
+ * @returns Created calendar event
8284
+ */
8285
+ async createAppointment(params) {
8286
+ await this.validateAppointmentParams(params);
8287
+ await this.validateClinicWorkingHours(params.clinicId, params.eventTime);
8288
+ await this.validateDoctorAvailability(
8289
+ params.doctorId,
8290
+ params.eventTime,
8291
+ params.clinicId
8292
+ );
8293
+ const { clinicInfo, practitionerInfo, patientInfo } = await this.fetchProfileInfoCards(
8294
+ params.clinicId,
8295
+ params.doctorId,
8296
+ params.patientId
8297
+ );
8298
+ const appointmentData = {
8299
+ clinicBranchId: params.clinicId,
8300
+ clinicBranchInfo: clinicInfo,
8301
+ practitionerProfileId: params.doctorId,
8302
+ practitionerProfileInfo: practitionerInfo,
8303
+ patientProfileId: params.patientId,
8304
+ patientProfileInfo: patientInfo,
8305
+ procedureId: params.procedureId,
8306
+ eventLocation: params.eventLocation,
8307
+ eventName: "Appointment",
8308
+ // TODO: Add procedure name when procedure model is available
8309
+ eventTime: params.eventTime,
8310
+ description: params.description || "",
8311
+ status: "pending" /* PENDING */,
8312
+ syncStatus: "internal" /* INTERNAL */,
8313
+ eventType: "appointment" /* APPOINTMENT */
8314
+ };
8315
+ const appointment = await createAppointmentUtil(
8316
+ this.db,
8317
+ params.clinicId,
8318
+ params.doctorId,
8319
+ params.patientId,
8320
+ appointmentData,
8321
+ this.generateId.bind(this)
8322
+ );
8323
+ await this.syncAppointmentWithExternalCalendars(appointment);
8324
+ return appointment;
8325
+ }
8326
+ /**
8327
+ * Updates an existing appointment
8328
+ * @param params - Appointment update parameters
8329
+ * @returns Updated calendar event
8330
+ */
8331
+ async updateAppointment(params) {
8332
+ await this.validateUpdatePermissions(params);
8333
+ const updateData = {
8334
+ eventTime: params.eventTime,
8335
+ description: params.description,
8336
+ status: params.status
8337
+ };
8338
+ const appointment = await updateAppointmentUtil(
8339
+ this.db,
8340
+ params.clinicId,
8341
+ params.doctorId,
8342
+ params.patientId,
8343
+ params.appointmentId,
8344
+ updateData
8345
+ );
8346
+ await this.syncAppointmentWithExternalCalendars(appointment);
8347
+ return appointment;
8348
+ }
8349
+ /**
8350
+ * Gets available appointment slots for a doctor at a clinic
8351
+ * @param clinicId - ID of the clinic
8352
+ * @param doctorId - ID of the doctor
8353
+ * @param date - Date to check availability for
8354
+ * @returns Array of available time slots
8355
+ */
8356
+ async getAvailableSlots(clinicId, doctorId, date) {
8357
+ const workingHours = await this.getClinicWorkingHours(clinicId, date);
8358
+ const doctorSchedule = await this.getDoctorSchedule(doctorId, date);
8359
+ const existingAppointments = await this.getDoctorAppointments(
8360
+ doctorId,
8361
+ date
8362
+ );
8363
+ return this.calculateAvailableSlots(
8364
+ workingHours,
8365
+ doctorSchedule,
8366
+ existingAppointments
8367
+ );
8368
+ }
8369
+ /**
8370
+ * Confirms an appointment
8371
+ * @param appointmentId - ID of the appointment
8372
+ * @param clinicId - ID of the clinic
8373
+ * @returns Confirmed calendar event
8374
+ */
8375
+ async confirmAppointment(appointmentId, clinicId) {
8376
+ return this.updateAppointmentStatus(
8377
+ appointmentId,
8378
+ clinicId,
8379
+ "confirmed" /* CONFIRMED */
8380
+ );
8381
+ }
8382
+ /**
8383
+ * Rejects an appointment
8384
+ * @param appointmentId - ID of the appointment
8385
+ * @param clinicId - ID of the clinic
8386
+ * @returns Rejected calendar event
8387
+ */
8388
+ async rejectAppointment(appointmentId, clinicId) {
8389
+ return this.updateAppointmentStatus(
8390
+ appointmentId,
8391
+ clinicId,
8392
+ "rejected" /* REJECTED */
8393
+ );
8394
+ }
8395
+ /**
8396
+ * Cancels an appointment
8397
+ * @param appointmentId - ID of the appointment
8398
+ * @param clinicId - ID of the clinic
8399
+ * @returns Canceled calendar event
8400
+ */
8401
+ async cancelAppointment(appointmentId, clinicId) {
8402
+ return this.updateAppointmentStatus(
8403
+ appointmentId,
8404
+ clinicId,
8405
+ "canceled" /* CANCELED */
8406
+ );
8407
+ }
8408
+ /**
8409
+ * Imports events from external calendars
8410
+ * @param entityType - Type of entity (practitioner or patient)
8411
+ * @param entityId - ID of the entity
8412
+ * @param startDate - Start date for fetching events
8413
+ * @param endDate - End date for fetching events
8414
+ * @returns Number of events imported
8415
+ */
8416
+ async importEventsFromExternalCalendars(entityType, entityId, startDate, endDate) {
8417
+ if (entityType === "patient") {
8418
+ return 0;
8419
+ }
8420
+ const syncedCalendars = await this.syncedCalendarsService.getPractitionerSyncedCalendars(
8421
+ entityId
8422
+ );
8423
+ const activeCalendars = syncedCalendars.filter((cal) => cal.isActive);
8424
+ if (activeCalendars.length === 0) {
8425
+ return 0;
8426
+ }
8427
+ let importedEventsCount = 0;
8428
+ const currentTime = import_firestore31.Timestamp.now();
8429
+ for (const calendar of activeCalendars) {
8430
+ try {
8431
+ let externalEvents = [];
8432
+ if (calendar.provider === "google" /* GOOGLE */) {
8433
+ externalEvents = await this.syncedCalendarsService.fetchEventsFromPractitionerGoogleCalendar(
8434
+ entityId,
8435
+ calendar.id,
8436
+ startDate,
8437
+ endDate
8438
+ );
8439
+ }
8440
+ for (const externalEvent of externalEvents) {
8441
+ try {
8442
+ const convertedEvent = this.syncedCalendarsService.convertGoogleEventsToPractitionerEvents(
8443
+ entityId,
8444
+ [externalEvent]
8445
+ )[0];
8446
+ if (!convertedEvent.eventTime) {
8447
+ continue;
8448
+ }
8449
+ const eventData = {
8450
+ // Ensure all required fields are set
8451
+ eventName: convertedEvent.eventName || "External Event",
8452
+ eventTime: convertedEvent.eventTime,
8453
+ description: convertedEvent.description || "",
8454
+ status: "confirmed" /* CONFIRMED */,
8455
+ syncStatus: "external" /* EXTERNAL */,
8456
+ eventType: "blocking" /* BLOCKING */,
8457
+ practitionerProfileId: entityId,
8458
+ syncedCalendarEventId: [
8459
+ {
8460
+ eventId: externalEvent.id,
8461
+ syncedCalendarProvider: calendar.provider,
8462
+ syncedAt: currentTime
8463
+ }
8464
+ ]
8465
+ };
8466
+ const doctorEvent = await this.createDoctorBlockingEvent(
8467
+ entityId,
8468
+ eventData
8469
+ );
8470
+ if (doctorEvent) {
8471
+ importedEventsCount++;
8472
+ }
8473
+ } catch (eventError) {
8474
+ console.error("Error importing event:", eventError);
8475
+ }
8476
+ }
8477
+ } catch (calendarError) {
8478
+ console.error(
8479
+ `Error fetching events from calendar ${calendar.id}:`,
8480
+ calendarError
8481
+ );
8482
+ }
8483
+ }
8484
+ return importedEventsCount;
8485
+ }
8486
+ /**
8487
+ * Creates a blocking event in a doctor's calendar
8488
+ * @param doctorId - ID of the doctor
8489
+ * @param eventData - Calendar event data
8490
+ * @returns Created calendar event
8491
+ */
8492
+ async createDoctorBlockingEvent(doctorId, eventData) {
8493
+ try {
8494
+ const eventId = this.generateId();
8495
+ const eventRef = (0, import_firestore32.doc)(
8496
+ this.db,
8497
+ PRACTITIONERS_COLLECTION,
8498
+ doctorId,
8499
+ CALENDAR_COLLECTION,
8500
+ eventId
8501
+ );
8502
+ const newEvent = {
8503
+ id: eventId,
8504
+ ...eventData,
8505
+ createdAt: (0, import_firestore31.serverTimestamp)(),
8506
+ updatedAt: (0, import_firestore31.serverTimestamp)()
8507
+ };
8508
+ await (0, import_firestore32.setDoc)(eventRef, newEvent);
8509
+ return {
8510
+ ...newEvent,
8511
+ createdAt: import_firestore31.Timestamp.now(),
8512
+ updatedAt: import_firestore31.Timestamp.now()
8513
+ };
8514
+ } catch (error) {
8515
+ console.error(
8516
+ `Error creating blocking event for doctor ${doctorId}:`,
8517
+ error
8518
+ );
8519
+ return null;
8520
+ }
8521
+ }
8522
+ /**
8523
+ * Periodically syncs events from external calendars for doctors
8524
+ * This would be called via a scheduled Cloud Function
8525
+ * @param lookbackDays - Number of days to look back for events
8526
+ * @param lookforwardDays - Number of days to look forward for events
8527
+ */
8528
+ async synchronizeExternalCalendars(lookbackDays = 7, lookforwardDays = 30) {
8529
+ try {
8530
+ const practitionersRef = (0, import_firestore32.collection)(this.db, PRACTITIONERS_COLLECTION);
8531
+ const practitionersSnapshot = await (0, import_firestore32.getDocs)(practitionersRef);
8532
+ const startDate = /* @__PURE__ */ new Date();
8533
+ startDate.setDate(startDate.getDate() - lookbackDays);
8534
+ const endDate = /* @__PURE__ */ new Date();
8535
+ endDate.setDate(endDate.getDate() + lookforwardDays);
8536
+ const syncPromises = [];
8537
+ for (const docSnapshot of practitionersSnapshot.docs) {
8538
+ const practitionerId = docSnapshot.id;
8539
+ syncPromises.push(
8540
+ this.importEventsFromExternalCalendars(
8541
+ "doctor",
8542
+ practitionerId,
8543
+ startDate,
8544
+ endDate
8545
+ ).then((count) => {
8546
+ console.log(
8547
+ `Imported ${count} events for doctor ${practitionerId}`
8548
+ );
8549
+ }).catch((error) => {
8550
+ console.error(
8551
+ `Error importing events for doctor ${practitionerId}:`,
8552
+ error
8553
+ );
8554
+ })
8555
+ );
8556
+ syncPromises.push(
8557
+ this.updateExistingEventsFromExternalCalendars(
8558
+ practitionerId,
8559
+ startDate,
8560
+ endDate
8561
+ ).then((count) => {
8562
+ console.log(
8563
+ `Updated ${count} events for doctor ${practitionerId}`
8564
+ );
8565
+ }).catch((error) => {
8566
+ console.error(
8567
+ `Error updating events for doctor ${practitionerId}:`,
8568
+ error
8569
+ );
8570
+ })
8571
+ );
8572
+ }
8573
+ await Promise.all(syncPromises);
8574
+ console.log("Completed external calendar synchronization");
8575
+ } catch (error) {
8576
+ console.error("Error synchronizing external calendars:", error);
8577
+ }
8578
+ }
8579
+ /**
8580
+ * Updates existing events that were synced from external calendars
8581
+ * @param doctorId - ID of the doctor
8582
+ * @param startDate - Start date for fetching events
8583
+ * @param endDate - End date for fetching events
8584
+ * @returns Number of events updated
8585
+ */
8586
+ async updateExistingEventsFromExternalCalendars(doctorId, startDate, endDate) {
8587
+ var _a;
8588
+ try {
8589
+ const eventsRef = (0, import_firestore32.collection)(
8590
+ this.db,
8591
+ PRACTITIONERS_COLLECTION,
8592
+ doctorId,
8593
+ CALENDAR_COLLECTION
8594
+ );
8595
+ const q = (0, import_firestore32.query)(
8596
+ eventsRef,
8597
+ (0, import_firestore32.where)("syncStatus", "==", "external" /* EXTERNAL */),
8598
+ (0, import_firestore32.where)("eventTime.start", ">=", import_firestore31.Timestamp.fromDate(startDate)),
8599
+ (0, import_firestore32.where)("eventTime.start", "<=", import_firestore31.Timestamp.fromDate(endDate))
8600
+ );
8601
+ const eventsSnapshot = await (0, import_firestore32.getDocs)(q);
8602
+ const events = eventsSnapshot.docs.map((doc20) => ({
8603
+ id: doc20.id,
8604
+ ...doc20.data()
8605
+ }));
8606
+ const calendars = await this.syncedCalendarsService.getPractitionerSyncedCalendars(
8607
+ doctorId
8608
+ );
8609
+ const activeCalendars = calendars.filter((cal) => cal.isActive);
8610
+ if (activeCalendars.length === 0 || events.length === 0) {
8611
+ return 0;
8612
+ }
8613
+ let updatedCount = 0;
8614
+ for (const event of events) {
8615
+ if (!((_a = event.syncedCalendarEventId) == null ? void 0 : _a.length)) continue;
8616
+ for (const syncId of event.syncedCalendarEventId) {
8617
+ const calendar = activeCalendars.find(
8618
+ (cal) => cal.provider === syncId.syncedCalendarProvider
8619
+ );
8620
+ if (!calendar) continue;
8621
+ if (syncId.syncedCalendarProvider === "google" /* GOOGLE */) {
8622
+ try {
8623
+ const externalEvent = await this.fetchExternalEvent(
8624
+ doctorId,
8625
+ calendar,
8626
+ syncId.eventId
8627
+ );
8628
+ if (externalEvent) {
8629
+ const externalStartTime = new Date(
8630
+ externalEvent.start.dateTime || externalEvent.start.date
8631
+ ).getTime();
8632
+ const externalEndTime = new Date(
8633
+ externalEvent.end.dateTime || externalEvent.end.date
8634
+ ).getTime();
8635
+ const localStartTime = event.eventTime.start.toDate().getTime();
8636
+ const localEndTime = event.eventTime.end.toDate().getTime();
8637
+ if (externalStartTime !== localStartTime || externalEndTime !== localEndTime || externalEvent.summary !== event.eventName || externalEvent.description !== event.description) {
8638
+ await this.updateLocalEventFromExternal(
8639
+ doctorId,
8640
+ event.id,
8641
+ externalEvent
8642
+ );
8643
+ updatedCount++;
8644
+ }
8645
+ } else {
8646
+ await this.updateEventStatus(
8647
+ doctorId,
8648
+ event.id,
8649
+ "canceled" /* CANCELED */
8650
+ );
8651
+ updatedCount++;
8652
+ }
8653
+ } catch (error) {
8654
+ console.error(
8655
+ `Error updating external event ${event.id}:`,
8656
+ error
8657
+ );
8658
+ }
8659
+ }
8660
+ }
8661
+ }
8662
+ return updatedCount;
8663
+ } catch (error) {
8664
+ console.error(
8665
+ "Error updating existing events from external calendars:",
8666
+ error
8667
+ );
8668
+ return 0;
8669
+ }
8670
+ }
8671
+ /**
8672
+ * Fetches a single external event from Google Calendar
8673
+ * @param doctorId - ID of the doctor
8674
+ * @param calendar - Calendar information
8675
+ * @param externalEventId - ID of the external event
8676
+ * @returns External event data or null if not found
8677
+ */
8678
+ async fetchExternalEvent(doctorId, calendar, externalEventId) {
8679
+ try {
8680
+ if (calendar.provider === "google" /* GOOGLE */) {
8681
+ const result = await this.syncedCalendarsService.fetchEventFromPractitionerGoogleCalendar(
8682
+ doctorId,
8683
+ calendar.id,
8684
+ externalEventId
8685
+ );
8686
+ return result;
8687
+ }
8688
+ return null;
8689
+ } catch (error) {
8690
+ console.error(`Error fetching external event ${externalEventId}:`, error);
8691
+ return null;
8692
+ }
8693
+ }
8694
+ /**
8695
+ * Updates a local event with data from an external event
8696
+ * @param doctorId - ID of the doctor
8697
+ * @param eventId - ID of the local event
8698
+ * @param externalEvent - External event data
8699
+ */
8700
+ async updateLocalEventFromExternal(doctorId, eventId, externalEvent) {
8701
+ try {
8702
+ const startTime = new Date(
8703
+ externalEvent.start.dateTime || externalEvent.start.date
8704
+ );
8705
+ const endTime = new Date(
8706
+ externalEvent.end.dateTime || externalEvent.end.date
8707
+ );
8708
+ const eventRef = (0, import_firestore32.doc)(
8709
+ this.db,
8710
+ PRACTITIONERS_COLLECTION,
8711
+ doctorId,
8712
+ CALENDAR_COLLECTION,
8713
+ eventId
8714
+ );
8715
+ await (0, import_firestore32.updateDoc)(eventRef, {
8716
+ eventName: externalEvent.summary || "External Event",
8717
+ eventTime: {
8718
+ start: import_firestore31.Timestamp.fromDate(startTime),
8719
+ end: import_firestore31.Timestamp.fromDate(endTime)
8720
+ },
8721
+ description: externalEvent.description || "",
8722
+ updatedAt: (0, import_firestore31.serverTimestamp)()
8723
+ });
8724
+ console.log(`Updated local event ${eventId} from external event`);
8725
+ } catch (error) {
8726
+ console.error(
8727
+ `Error updating local event ${eventId} from external:`,
8728
+ error
8729
+ );
8730
+ }
8731
+ }
8732
+ /**
8733
+ * Updates an event's status
8734
+ * @param doctorId - ID of the doctor
8735
+ * @param eventId - ID of the event
8736
+ * @param status - New status
8737
+ */
8738
+ async updateEventStatus(doctorId, eventId, status) {
8739
+ try {
8740
+ const eventRef = (0, import_firestore32.doc)(
8741
+ this.db,
8742
+ PRACTITIONERS_COLLECTION,
8743
+ doctorId,
8744
+ CALENDAR_COLLECTION,
8745
+ eventId
8746
+ );
8747
+ await (0, import_firestore32.updateDoc)(eventRef, {
8748
+ status,
8749
+ updatedAt: (0, import_firestore31.serverTimestamp)()
8750
+ });
8751
+ console.log(`Updated event ${eventId} status to ${status}`);
8752
+ } catch (error) {
8753
+ console.error(`Error updating event ${eventId} status:`, error);
8754
+ }
8755
+ }
8756
+ /**
8757
+ * Creates a scheduled job to periodically sync external calendars
8758
+ * Note: This would be implemented using Cloud Functions in a real application
8759
+ * This is a sample implementation to show how it could be set up
8760
+ * @param interval - Interval in hours
8761
+ */
8762
+ createScheduledSyncJob(interval = 3) {
8763
+ console.log(
8764
+ `Setting up scheduled calendar sync job every ${interval} hours`
8765
+ );
8766
+ }
8767
+ // #endregion
8768
+ // #region Private Helper Methods
8769
+ /**
8770
+ * Validates appointment creation parameters
8771
+ * @param params - Appointment parameters to validate
8772
+ * @throws Error if validation fails
8773
+ */
8774
+ async validateAppointmentParams(params) {
8775
+ await createAppointmentSchema.parseAsync(params);
8776
+ }
8777
+ /**
8778
+ * Validates if the event time falls within clinic working hours
8779
+ * @param clinicId - ID of the clinic
8780
+ * @param eventTime - Event time to validate
8781
+ * @throws Error if validation fails
8782
+ */
8783
+ async validateClinicWorkingHours(clinicId, eventTime) {
8784
+ const startDate = eventTime.start.toDate();
8785
+ const workingHours = await this.getClinicWorkingHours(clinicId, startDate);
8786
+ if (workingHours.length === 0) {
8787
+ throw new Error("Clinic is not open on this day");
8788
+ }
8789
+ const startTime = startDate;
8790
+ const endTime = eventTime.end.toDate();
8791
+ const isWithinWorkingHours = workingHours.some((slot) => {
8792
+ return slot.start <= startTime && slot.end >= endTime && slot.isAvailable;
8793
+ });
8794
+ if (!isWithinWorkingHours) {
8795
+ throw new Error("Appointment time is outside clinic working hours");
8796
+ }
8797
+ }
8798
+ /**
8799
+ * Validates if the doctor is available during the event time
8800
+ * @param doctorId - ID of the doctor
8801
+ * @param eventTime - Event time to validate
8802
+ * @param clinicId - ID of the clinic where the appointment is being booked
8803
+ * @throws Error if validation fails
8804
+ */
8805
+ async validateDoctorAvailability(doctorId, eventTime, clinicId) {
8806
+ var _a;
8807
+ const startDate = eventTime.start.toDate();
8808
+ const startTime = startDate;
8809
+ const endTime = eventTime.end.toDate();
8810
+ const practitionerRef = (0, import_firestore32.doc)(this.db, PRACTITIONERS_COLLECTION, doctorId);
8811
+ const practitionerDoc = await (0, import_firestore32.getDoc)(practitionerRef);
8812
+ if (!practitionerDoc.exists()) {
8813
+ throw new Error(`Doctor with ID ${doctorId} not found`);
8814
+ }
8815
+ const practitioner = practitionerDoc.data();
8816
+ if (!practitioner.clinics.includes(clinicId)) {
8817
+ throw new Error("Doctor does not work at this clinic");
8818
+ }
8819
+ const clinicWorkingHours = (_a = practitioner.clinicWorkingHours) == null ? void 0 : _a.find(
8820
+ (hours) => hours.clinicId === clinicId && hours.isActive
8821
+ );
8822
+ if (!clinicWorkingHours) {
8823
+ throw new Error("Doctor does not have working hours set for this clinic");
8824
+ }
8825
+ const dayOfWeek = startDate.getDay();
8826
+ const dayKey = [
8827
+ "sunday",
8828
+ "monday",
8829
+ "tuesday",
8830
+ "wednesday",
8831
+ "thursday",
8832
+ "friday",
8833
+ "saturday"
8834
+ ][dayOfWeek];
8835
+ const daySchedule = clinicWorkingHours.workingHours[dayKey];
8836
+ if (!daySchedule) {
8837
+ throw new Error("Doctor is not working on this day at this clinic");
8838
+ }
8839
+ const [startHour, startMinute] = daySchedule.start.split(":").map(Number);
8840
+ const [endHour, endMinute] = daySchedule.end.split(":").map(Number);
8841
+ const scheduleStart = new Date(startDate);
8842
+ scheduleStart.setHours(startHour, startMinute, 0, 0);
8843
+ const scheduleEnd = new Date(startDate);
8844
+ scheduleEnd.setHours(endHour, endMinute, 0, 0);
8845
+ if (startTime < scheduleStart || endTime > scheduleEnd) {
8846
+ throw new Error(
8847
+ "Appointment time is outside doctor's working hours at this clinic"
8848
+ );
8849
+ }
8850
+ const appointments = await this.getDoctorAppointments(doctorId, startDate);
8851
+ const hasOverlap = appointments.some((appointment) => {
8852
+ const appointmentStart = appointment.eventTime.start.toDate();
8853
+ const appointmentEnd = appointment.eventTime.end.toDate();
8854
+ return startTime >= appointmentStart && startTime < appointmentEnd || endTime > appointmentStart && endTime <= appointmentEnd || startTime <= appointmentStart && endTime >= appointmentEnd;
8855
+ });
8856
+ if (hasOverlap) {
8857
+ throw new Error("Doctor has another appointment during this time");
8858
+ }
8859
+ }
8860
+ /**
8861
+ * Updates appointment status
8862
+ * @param appointmentId - ID of the appointment
8863
+ * @param clinicId - ID of the clinic
8864
+ * @param status - New status
8865
+ * @returns Updated calendar event
8866
+ */
8867
+ async updateAppointmentStatus(appointmentId, clinicId, status) {
8868
+ const appointmentRef = (0, import_firestore32.doc)(this.db, CALENDAR_COLLECTION, appointmentId);
8869
+ const appointmentDoc = await (0, import_firestore32.getDoc)(appointmentRef);
8870
+ if (!appointmentDoc.exists()) {
8871
+ throw new Error(`Appointment with ID ${appointmentId} not found`);
8872
+ }
8873
+ const appointment = appointmentDoc.data();
8874
+ if (appointment.clinicBranchId !== clinicId) {
8875
+ throw new Error("Appointment does not belong to the specified clinic");
8876
+ }
8877
+ this.validateStatusTransition(appointment.status, status);
8878
+ const updateParams = {
8879
+ appointmentId,
8880
+ clinicId,
8881
+ doctorId: appointment.practitionerProfileId || "",
8882
+ patientId: appointment.patientProfileId || "",
8883
+ status
8884
+ };
8885
+ await this.validateUpdatePermissions(updateParams);
8886
+ return this.updateAppointment(updateParams);
8887
+ }
8888
+ /**
8889
+ * Validates status transition
8890
+ * @param currentStatus - Current status
8891
+ * @param newStatus - New status
8892
+ * @throws Error if transition is invalid
8893
+ */
8894
+ validateStatusTransition(currentStatus, newStatus) {
8895
+ const validTransitions = {
8896
+ ["pending" /* PENDING */]: [
8897
+ "confirmed" /* CONFIRMED */,
8898
+ "rejected" /* REJECTED */,
8899
+ "canceled" /* CANCELED */
8900
+ ],
8901
+ ["confirmed" /* CONFIRMED */]: [
8902
+ "canceled" /* CANCELED */,
8903
+ "completed" /* COMPLETED */,
8904
+ "rescheduled" /* RESCHEDULED */
8905
+ ],
8906
+ ["rejected" /* REJECTED */]: [],
8907
+ ["canceled" /* CANCELED */]: [],
8908
+ ["rescheduled" /* RESCHEDULED */]: [
8909
+ "confirmed" /* CONFIRMED */,
8910
+ "canceled" /* CANCELED */
8911
+ ],
8912
+ ["completed" /* COMPLETED */]: []
8913
+ };
8914
+ if (!validTransitions[currentStatus].includes(newStatus)) {
8915
+ throw new Error(
8916
+ `Invalid status transition from ${currentStatus} to ${newStatus}`
8917
+ );
8918
+ }
8919
+ }
8920
+ /**
8921
+ * Syncs appointment with external calendars based on entity type and status
8922
+ * @param appointment - Calendar event to sync
8923
+ */
8924
+ async syncAppointmentWithExternalCalendars(appointment) {
8925
+ if (!appointment.practitionerProfileId || !appointment.patientProfileId) {
8926
+ return;
8927
+ }
8928
+ try {
8929
+ const [doctorCalendars, patientCalendars] = await Promise.all([
8930
+ this.syncedCalendarsService.getPractitionerSyncedCalendars(
8931
+ appointment.practitionerProfileId
8932
+ ),
8933
+ this.syncedCalendarsService.getPatientSyncedCalendars(
8934
+ appointment.patientProfileId
8935
+ )
8936
+ ]);
8937
+ const activeDoctorCalendars = doctorCalendars.filter(
8938
+ (cal) => cal.isActive
8939
+ );
8940
+ const activePatientCalendars = patientCalendars.filter(
8941
+ (cal) => cal.isActive
8942
+ );
8943
+ if (activeDoctorCalendars.length === 0 && activePatientCalendars.length === 0) {
8944
+ return;
8945
+ }
8946
+ if (appointment.syncStatus !== "internal" /* INTERNAL */) {
8947
+ return;
8948
+ }
8949
+ if (appointment.status === "confirmed" /* CONFIRMED */ && activeDoctorCalendars.length > 0) {
8950
+ await Promise.all(
8951
+ activeDoctorCalendars.map(
8952
+ (calendar) => this.syncEventToExternalCalendar(appointment, calendar, "doctor")
8953
+ )
8954
+ );
8955
+ }
8956
+ if (appointment.status !== "canceled" /* CANCELED */ && appointment.status !== "rejected" /* REJECTED */ && activePatientCalendars.length > 0) {
8957
+ await Promise.all(
8958
+ activePatientCalendars.map(
8959
+ (calendar) => this.syncEventToExternalCalendar(appointment, calendar, "patient")
8960
+ )
8961
+ );
8962
+ }
8963
+ } catch (error) {
8964
+ console.error("Error syncing with external calendars:", error);
8965
+ }
8966
+ }
8967
+ /**
8968
+ * Syncs a single event to an external calendar
8969
+ * @param appointment - Calendar event to sync
8970
+ * @param calendar - External calendar to sync with
8971
+ * @param entityType - Type of entity owning the calendar
8972
+ */
8973
+ async syncEventToExternalCalendar(appointment, calendar, entityType) {
8974
+ var _a, _b, _c, _d, _e;
8975
+ try {
8976
+ const eventToSync = { ...appointment };
8977
+ let eventTitle = appointment.eventName;
8978
+ const clinicName = ((_a = appointment.clinicBranchInfo) == null ? void 0 : _a.name) || "Clinic";
8979
+ if (entityType === "patient") {
8980
+ eventTitle = `[${appointment.status}] ${eventTitle} @ ${clinicName}`;
8981
+ } else {
8982
+ eventTitle = `${eventTitle} - Patient: ${((_b = appointment.patientProfileInfo) == null ? void 0 : _b.fullName) || "Unknown"} @ ${clinicName}`;
8983
+ }
8984
+ eventToSync.eventName = eventTitle;
8985
+ const existingSyncId = (_d = (_c = appointment.syncedCalendarEventId) == null ? void 0 : _c.find(
8986
+ (sync) => sync.syncedCalendarProvider === calendar.provider
8987
+ )) == null ? void 0 : _d.eventId;
8988
+ if (calendar.provider === "google" /* GOOGLE */) {
8989
+ const result = await this.syncedCalendarsService.syncPractitionerEventsToGoogleCalendar(
8990
+ entityType === "doctor" ? appointment.practitionerProfileId : appointment.patientProfileId,
8991
+ calendar.id,
8992
+ [eventToSync],
8993
+ existingSyncId
8994
+ // Pass existing sync ID if we have one
8995
+ );
8996
+ if (result.success && ((_e = result.eventIds) == null ? void 0 : _e.length) && !existingSyncId) {
8997
+ const newSyncEvent = {
8998
+ eventId: result.eventIds[0],
8999
+ syncedCalendarProvider: calendar.provider,
9000
+ syncedAt: import_firestore31.Timestamp.now()
9001
+ };
9002
+ await this.updateEventWithSyncId(
9003
+ entityType === "doctor" ? appointment.practitionerProfileId : appointment.patientProfileId,
9004
+ entityType,
9005
+ appointment.id,
9006
+ newSyncEvent
9007
+ );
9008
+ }
9009
+ }
9010
+ } catch (error) {
9011
+ console.error(`Error syncing with ${entityType}'s calendar:`, error);
9012
+ }
9013
+ }
9014
+ /**
9015
+ * Updates an event with a new sync ID
9016
+ * @param entityId - ID of the entity (doctor or patient)
9017
+ * @param entityType - Type of entity
9018
+ * @param eventId - ID of the event
9019
+ * @param syncEvent - Sync event information
9020
+ */
9021
+ async updateEventWithSyncId(entityId, entityType, eventId, syncEvent) {
9022
+ try {
9023
+ const collectionPath = entityType === "doctor" ? `${PRACTITIONERS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}` : `${PATIENTS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
9024
+ const eventRef = (0, import_firestore32.doc)(this.db, collectionPath, eventId);
9025
+ const eventDoc = await (0, import_firestore32.getDoc)(eventRef);
9026
+ if (eventDoc.exists()) {
9027
+ const event = eventDoc.data();
9028
+ const syncIds = [...event.syncedCalendarEventId || []];
9029
+ const existingSyncIndex = syncIds.findIndex(
9030
+ (sync) => sync.syncedCalendarProvider === syncEvent.syncedCalendarProvider
9031
+ );
9032
+ if (existingSyncIndex >= 0) {
9033
+ syncIds[existingSyncIndex] = syncEvent;
9034
+ } else {
9035
+ syncIds.push(syncEvent);
9036
+ }
9037
+ await (0, import_firestore32.updateDoc)(eventRef, {
9038
+ syncedCalendarEventId: syncIds,
9039
+ updatedAt: (0, import_firestore31.serverTimestamp)()
9040
+ });
9041
+ console.log(
9042
+ `Updated event ${eventId} with sync ID ${syncEvent.eventId}`
9043
+ );
9044
+ }
9045
+ } catch (error) {
9046
+ console.error("Error updating event with sync ID:", error);
9047
+ }
9048
+ }
9049
+ /**
9050
+ * Validates update permissions and parameters
9051
+ * @param params - Update parameters to validate
9052
+ */
9053
+ async validateUpdatePermissions(params) {
9054
+ await updateAppointmentSchema.parseAsync(params);
9055
+ }
9056
+ /**
9057
+ * Gets clinic working hours for a specific date
9058
+ * @param clinicId - ID of the clinic
9059
+ * @param date - Date to get working hours for
9060
+ * @returns Working hours for the clinic
9061
+ */
9062
+ async getClinicWorkingHours(clinicId, date) {
9063
+ const clinicRef = (0, import_firestore32.doc)(this.db, CLINICS_COLLECTION, clinicId);
9064
+ const clinicDoc = await (0, import_firestore32.getDoc)(clinicRef);
9065
+ if (!clinicDoc.exists()) {
9066
+ throw new Error(`Clinic with ID ${clinicId} not found`);
9067
+ }
9068
+ const workingHours = [];
9069
+ const dayOfWeek = date.getDay();
9070
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
9071
+ return workingHours;
9072
+ }
9073
+ const workingDate = new Date(date);
9074
+ workingDate.setHours(9, 0, 0, 0);
9075
+ const startTime = new Date(workingDate);
9076
+ workingDate.setHours(17, 0, 0, 0);
9077
+ const endTime = new Date(workingDate);
9078
+ workingHours.push({
9079
+ start: startTime,
9080
+ end: endTime,
9081
+ isAvailable: true
9082
+ });
9083
+ return workingHours;
9084
+ }
9085
+ /**
9086
+ * Gets doctor's schedule for a specific date
9087
+ * @param doctorId - ID of the doctor
9088
+ * @param date - Date to get schedule for
9089
+ * @returns Doctor's schedule
9090
+ */
9091
+ async getDoctorSchedule(doctorId, date) {
9092
+ const practitionerRef = (0, import_firestore32.doc)(this.db, PRACTITIONERS_COLLECTION, doctorId);
9093
+ const practitionerDoc = await (0, import_firestore32.getDoc)(practitionerRef);
9094
+ if (!practitionerDoc.exists()) {
9095
+ throw new Error(`Doctor with ID ${doctorId} not found`);
9096
+ }
9097
+ const schedule = [];
9098
+ const dayOfWeek = date.getDay();
9099
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
9100
+ return schedule;
9101
+ }
9102
+ const scheduleDate = new Date(date);
9103
+ scheduleDate.setHours(9, 0, 0, 0);
9104
+ const startTime = new Date(scheduleDate);
9105
+ scheduleDate.setHours(17, 0, 0, 0);
9106
+ const endTime = new Date(scheduleDate);
9107
+ schedule.push({
9108
+ start: startTime,
9109
+ end: endTime,
9110
+ isAvailable: true
9111
+ });
9112
+ return schedule;
9113
+ }
9114
+ /**
9115
+ * Gets doctor's appointments for a specific date
9116
+ * @param doctorId - ID of the doctor
9117
+ * @param date - Date to get appointments for
9118
+ * @returns Array of calendar events
9119
+ */
9120
+ async getDoctorAppointments(doctorId, date) {
9121
+ const startOfDay = new Date(date);
9122
+ startOfDay.setHours(0, 0, 0, 0);
9123
+ const endOfDay = new Date(date);
9124
+ endOfDay.setHours(23, 59, 59, 999);
9125
+ const appointmentsRef = (0, import_firestore32.collection)(this.db, CALENDAR_COLLECTION);
9126
+ const q = (0, import_firestore32.query)(
9127
+ appointmentsRef,
9128
+ (0, import_firestore32.where)("practitionerProfileId", "==", doctorId),
9129
+ (0, import_firestore32.where)("eventTime.start", ">=", import_firestore31.Timestamp.fromDate(startOfDay)),
9130
+ (0, import_firestore32.where)("eventTime.start", "<=", import_firestore31.Timestamp.fromDate(endOfDay)),
9131
+ (0, import_firestore32.where)("status", "in", [
9132
+ "confirmed" /* CONFIRMED */,
9133
+ "pending" /* PENDING */
9134
+ ])
9135
+ );
9136
+ const querySnapshot = await (0, import_firestore32.getDocs)(q);
9137
+ return querySnapshot.docs.map((doc20) => doc20.data());
9138
+ }
9139
+ /**
9140
+ * Calculates available time slots based on working hours, schedule and existing appointments
9141
+ * @param workingHours - Clinic working hours
9142
+ * @param doctorSchedule - Doctor's schedule
9143
+ * @param existingAppointments - Existing appointments
9144
+ * @returns Array of available time slots
9145
+ */
9146
+ calculateAvailableSlots(workingHours, doctorSchedule, existingAppointments) {
9147
+ const availableSlots = [];
9148
+ for (const workingHour of workingHours) {
9149
+ for (const scheduleSlot of doctorSchedule) {
9150
+ const overlapStart = new Date(
9151
+ Math.max(workingHour.start.getTime(), scheduleSlot.start.getTime())
9152
+ );
9153
+ const overlapEnd = new Date(
9154
+ Math.min(workingHour.end.getTime(), scheduleSlot.end.getTime())
9155
+ );
9156
+ if (overlapStart < overlapEnd && workingHour.isAvailable && scheduleSlot.isAvailable) {
9157
+ let slotStart = new Date(overlapStart);
9158
+ while (slotStart < overlapEnd) {
9159
+ const slotEnd = new Date(
9160
+ slotStart.getTime() + MIN_APPOINTMENT_DURATION2 * 60 * 1e3
9161
+ );
9162
+ const hasOverlap = existingAppointments.some((appointment) => {
9163
+ const appointmentStart = appointment.eventTime.start.toDate();
9164
+ const appointmentEnd = appointment.eventTime.end.toDate();
9165
+ return slotStart >= appointmentStart && slotStart < appointmentEnd || slotEnd > appointmentStart && slotEnd <= appointmentEnd;
9166
+ });
9167
+ if (!hasOverlap && slotEnd <= overlapEnd) {
9168
+ availableSlots.push({
9169
+ start: new Date(slotStart),
9170
+ end: new Date(slotEnd),
9171
+ isAvailable: true
9172
+ });
9173
+ }
9174
+ slotStart = new Date(
9175
+ slotStart.getTime() + MIN_APPOINTMENT_DURATION2 * 60 * 1e3
9176
+ );
9177
+ }
9178
+ }
9179
+ }
9180
+ }
9181
+ return availableSlots;
9182
+ }
9183
+ /**
9184
+ * Fetches and creates info cards for clinic, doctor, and patient profiles
9185
+ * @param clinicId - ID of the clinic
9186
+ * @param doctorId - ID of the doctor
9187
+ * @param patientId - ID of the patient
9188
+ * @returns Object containing info cards for all profiles
9189
+ */
9190
+ async fetchProfileInfoCards(clinicId, doctorId, patientId) {
9191
+ var _a;
9192
+ try {
9193
+ const [clinicDoc, practitionerDoc, patientDoc, patientSensitiveInfoDoc] = await Promise.all([
9194
+ (0, import_firestore32.getDoc)((0, import_firestore32.doc)(this.db, CLINICS_COLLECTION, clinicId)),
9195
+ (0, import_firestore32.getDoc)((0, import_firestore32.doc)(this.db, PRACTITIONERS_COLLECTION, doctorId)),
9196
+ (0, import_firestore32.getDoc)((0, import_firestore32.doc)(this.db, PATIENTS_COLLECTION, patientId)),
9197
+ (0, import_firestore32.getDoc)(
9198
+ (0, import_firestore32.doc)(
9199
+ this.db,
9200
+ PATIENTS_COLLECTION,
9201
+ patientId,
9202
+ PATIENT_SENSITIVE_INFO_COLLECTION,
9203
+ patientId
9204
+ )
9205
+ )
9206
+ ]);
9207
+ const clinicInfo = clinicDoc.exists() ? {
9208
+ id: clinicDoc.id,
9209
+ featuredPhoto: clinicDoc.data().featuredPhoto || "",
9210
+ name: clinicDoc.data().name,
9211
+ description: clinicDoc.data().description || "",
9212
+ location: clinicDoc.data().location,
9213
+ contactInfo: clinicDoc.data().contactInfo
9214
+ } : null;
9215
+ const practitionerInfo = practitionerDoc.exists() ? {
9216
+ id: practitionerDoc.id,
9217
+ practitionerPhoto: practitionerDoc.data().basicInfo.profileImageUrl || null,
9218
+ name: `${practitionerDoc.data().basicInfo.firstName} ${practitionerDoc.data().basicInfo.lastName}`,
9219
+ email: practitionerDoc.data().basicInfo.email,
9220
+ phone: practitionerDoc.data().basicInfo.phoneNumber || null,
9221
+ certification: practitionerDoc.data().certification
9222
+ } : null;
9223
+ let patientInfo = null;
9224
+ if (patientSensitiveInfoDoc.exists()) {
9225
+ const sensitiveData = patientSensitiveInfoDoc.data();
9226
+ patientInfo = {
9227
+ id: patientId,
9228
+ fullName: `${sensitiveData.firstName} ${sensitiveData.lastName}`,
9229
+ email: sensitiveData.email || "",
9230
+ phone: sensitiveData.phoneNumber || null,
9231
+ dateOfBirth: sensitiveData.dateOfBirth || import_firestore31.Timestamp.now(),
9232
+ gender: sensitiveData.gender || "other" /* OTHER */
9233
+ };
9234
+ } else if (patientDoc.exists()) {
9235
+ patientInfo = {
9236
+ id: patientDoc.id,
9237
+ fullName: patientDoc.data().displayName,
9238
+ email: ((_a = patientDoc.data().contactInfo) == null ? void 0 : _a.email) || "",
9239
+ phone: patientDoc.data().phoneNumber || null,
9240
+ dateOfBirth: patientDoc.data().dateOfBirth || import_firestore31.Timestamp.now(),
9241
+ gender: patientDoc.data().gender || "other" /* OTHER */
9242
+ };
9243
+ }
9244
+ return {
9245
+ clinicInfo,
9246
+ practitionerInfo,
9247
+ patientInfo
9248
+ };
9249
+ } catch (error) {
9250
+ console.error("Error fetching profile info cards:", error);
9251
+ return {
9252
+ clinicInfo: null,
9253
+ practitionerInfo: null,
9254
+ patientInfo: null
9255
+ };
9256
+ }
9257
+ }
9258
+ // #endregion
9259
+ };
9260
+
9261
+ // src/validations/notification.schema.ts
9262
+ var import_zod18 = require("zod");
9263
+ var baseNotificationSchema = import_zod18.z.object({
9264
+ id: import_zod18.z.string().optional(),
9265
+ userId: import_zod18.z.string(),
9266
+ notificationTime: import_zod18.z.any(),
6376
9267
  // Timestamp
6377
- notificationType: import_zod16.z.nativeEnum(NotificationType),
6378
- notificationTokens: import_zod16.z.array(import_zod16.z.string()),
6379
- status: import_zod16.z.nativeEnum(NotificationStatus),
6380
- createdAt: import_zod16.z.any().optional(),
9268
+ notificationType: import_zod18.z.nativeEnum(NotificationType),
9269
+ notificationTokens: import_zod18.z.array(import_zod18.z.string()),
9270
+ status: import_zod18.z.nativeEnum(NotificationStatus),
9271
+ createdAt: import_zod18.z.any().optional(),
6381
9272
  // Timestamp
6382
- updatedAt: import_zod16.z.any().optional(),
9273
+ updatedAt: import_zod18.z.any().optional(),
6383
9274
  // Timestamp
6384
- title: import_zod16.z.string(),
6385
- body: import_zod16.z.string(),
6386
- isRead: import_zod16.z.boolean(),
6387
- userRole: import_zod16.z.nativeEnum(UserRole)
9275
+ title: import_zod18.z.string(),
9276
+ body: import_zod18.z.string(),
9277
+ isRead: import_zod18.z.boolean(),
9278
+ userRole: import_zod18.z.nativeEnum(UserRole)
6388
9279
  });
6389
9280
  var preRequirementNotificationSchema = baseNotificationSchema.extend({
6390
- notificationType: import_zod16.z.literal("preRequirement" /* PRE_REQUIREMENT */),
6391
- treatmentId: import_zod16.z.string(),
6392
- requirements: import_zod16.z.array(import_zod16.z.string()),
6393
- deadline: import_zod16.z.any()
9281
+ notificationType: import_zod18.z.literal("preRequirement" /* PRE_REQUIREMENT */),
9282
+ treatmentId: import_zod18.z.string(),
9283
+ requirements: import_zod18.z.array(import_zod18.z.string()),
9284
+ deadline: import_zod18.z.any()
6394
9285
  // Timestamp
6395
9286
  });
6396
9287
  var postRequirementNotificationSchema = baseNotificationSchema.extend({
6397
- notificationType: import_zod16.z.literal("postRequirement" /* POST_REQUIREMENT */),
6398
- treatmentId: import_zod16.z.string(),
6399
- requirements: import_zod16.z.array(import_zod16.z.string()),
6400
- deadline: import_zod16.z.any()
9288
+ notificationType: import_zod18.z.literal("postRequirement" /* POST_REQUIREMENT */),
9289
+ treatmentId: import_zod18.z.string(),
9290
+ requirements: import_zod18.z.array(import_zod18.z.string()),
9291
+ deadline: import_zod18.z.any()
6401
9292
  // Timestamp
6402
9293
  });
6403
9294
  var appointmentReminderNotificationSchema = baseNotificationSchema.extend({
6404
- notificationType: import_zod16.z.literal("appointmentReminder" /* APPOINTMENT_REMINDER */),
6405
- appointmentId: import_zod16.z.string(),
6406
- appointmentTime: import_zod16.z.any(),
9295
+ notificationType: import_zod18.z.literal("appointmentReminder" /* APPOINTMENT_REMINDER */),
9296
+ appointmentId: import_zod18.z.string(),
9297
+ appointmentTime: import_zod18.z.any(),
6407
9298
  // Timestamp
6408
- treatmentType: import_zod16.z.string(),
6409
- doctorName: import_zod16.z.string()
9299
+ treatmentType: import_zod18.z.string(),
9300
+ doctorName: import_zod18.z.string()
6410
9301
  });
6411
9302
  var appointmentNotificationSchema = baseNotificationSchema.extend({
6412
- notificationType: import_zod16.z.literal("appointmentNotification" /* APPOINTMENT_NOTIFICATION */),
6413
- appointmentId: import_zod16.z.string(),
6414
- appointmentStatus: import_zod16.z.string(),
6415
- previousStatus: import_zod16.z.string(),
6416
- reason: import_zod16.z.string().optional()
9303
+ notificationType: import_zod18.z.literal("appointmentNotification" /* APPOINTMENT_NOTIFICATION */),
9304
+ appointmentId: import_zod18.z.string(),
9305
+ appointmentStatus: import_zod18.z.string(),
9306
+ previousStatus: import_zod18.z.string(),
9307
+ reason: import_zod18.z.string().optional()
6417
9308
  });
6418
- var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
9309
+ var notificationSchema = import_zod18.z.discriminatedUnion("notificationType", [
6419
9310
  preRequirementNotificationSchema,
6420
9311
  postRequirementNotificationSchema,
6421
9312
  appointmentReminderNotificationSchema,
@@ -6428,9 +9319,14 @@ var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
6428
9319
  AllergyType,
6429
9320
  AuthService,
6430
9321
  BlockingCondition,
9322
+ CALENDAR_COLLECTION,
6431
9323
  CLINICS_COLLECTION,
6432
9324
  CLINIC_ADMINS_COLLECTION,
6433
9325
  CLINIC_GROUPS_COLLECTION,
9326
+ CalendarEventStatus,
9327
+ CalendarEventType,
9328
+ CalendarServiceV2,
9329
+ CalendarSyncStatus,
6434
9330
  CertificationLevel,
6435
9331
  CertificationSpecialty,
6436
9332
  ClinicAdminService,
@@ -6469,9 +9365,15 @@ var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
6469
9365
  PatientService,
6470
9366
  PracticeType,
6471
9367
  PractitionerService,
9368
+ PractitionerStatus,
9369
+ PractitionerTokenStatus,
6472
9370
  PricingMeasure,
6473
9371
  ProcedureFamily,
9372
+ REGISTER_TOKENS_COLLECTION,
9373
+ SYNCED_CALENDARS_COLLECTION,
6474
9374
  SubscriptionModel,
9375
+ SyncedCalendarProvider,
9376
+ SyncedCalendarsService,
6475
9377
  TreatmentBenefit,
6476
9378
  USER_ERRORS,
6477
9379
  UserService,
@@ -6488,6 +9390,8 @@ var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
6488
9390
  appointmentReminderNotificationSchema,
6489
9391
  baseNotificationSchema,
6490
9392
  blockingConditionSchema,
9393
+ calendarEventSchema,
9394
+ calendarEventTimeSchema,
6491
9395
  clinicAdminOptionsSchema,
6492
9396
  clinicAdminSchema,
6493
9397
  clinicAdminSignupSchema,
@@ -6503,16 +9407,20 @@ var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
6503
9407
  contactPersonSchema,
6504
9408
  contraindicationSchema,
6505
9409
  createAdminTokenSchema,
9410
+ createAppointmentSchema,
9411
+ createCalendarEventSchema,
6506
9412
  createClinicAdminSchema,
6507
9413
  createClinicGroupSchema,
6508
9414
  createClinicSchema,
6509
9415
  createDefaultClinicGroupSchema,
6510
9416
  createDocumentTemplateSchema,
9417
+ createDraftPractitionerSchema,
6511
9418
  createPatientLocationInfoSchema,
6512
9419
  createPatientMedicalInfoSchema,
6513
9420
  createPatientProfileSchema,
6514
9421
  createPatientSensitiveInfoSchema,
6515
9422
  createPractitionerSchema,
9423
+ createPractitionerTokenSchema,
6516
9424
  createUserOptionsSchema,
6517
9425
  doctorInfoSchema,
6518
9426
  documentElementSchema,
@@ -6534,21 +9442,31 @@ var notificationSchema = import_zod16.z.discriminatedUnion("notificationType", [
6534
9442
  patientDoctorSchema,
6535
9443
  patientLocationInfoSchema,
6536
9444
  patientMedicalInfoSchema,
9445
+ patientProfileInfoSchema,
6537
9446
  patientProfileSchema,
6538
9447
  patientSensitiveInfoSchema,
6539
9448
  postRequirementNotificationSchema,
6540
9449
  practitionerBasicInfoSchema,
6541
9450
  practitionerCertificationSchema,
6542
9451
  practitionerClinicProceduresSchema,
9452
+ practitionerClinicWorkingHoursSchema,
9453
+ practitionerProfileInfoSchema,
6543
9454
  practitionerReviewSchema,
6544
9455
  practitionerSchema,
9456
+ practitionerTokenSchema,
6545
9457
  practitionerWorkingHoursSchema,
6546
9458
  preRequirementNotificationSchema,
9459
+ procedureCategorizationSchema,
9460
+ procedureInfoSchema,
6547
9461
  reviewInfoSchema,
6548
9462
  serviceInfoSchema,
9463
+ syncedCalendarEventSchema,
9464
+ timeSlotSchema,
6549
9465
  timestampSchema,
6550
9466
  updateAllergySchema,
9467
+ updateAppointmentSchema,
6551
9468
  updateBlockingConditionSchema,
9469
+ updateCalendarEventSchema,
6552
9470
  updateClinicAdminSchema,
6553
9471
  updateClinicGroupSchema,
6554
9472
  updateClinicSchema,