@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.mjs CHANGED
@@ -108,6 +108,31 @@ var FilledDocumentStatus = /* @__PURE__ */ ((FilledDocumentStatus2) => {
108
108
  return FilledDocumentStatus2;
109
109
  })(FilledDocumentStatus || {});
110
110
 
111
+ // src/types/calendar/index.ts
112
+ var CalendarEventStatus = /* @__PURE__ */ ((CalendarEventStatus3) => {
113
+ CalendarEventStatus3["PENDING"] = "pending";
114
+ CalendarEventStatus3["CONFIRMED"] = "confirmed";
115
+ CalendarEventStatus3["REJECTED"] = "rejected";
116
+ CalendarEventStatus3["CANCELED"] = "canceled";
117
+ CalendarEventStatus3["RESCHEDULED"] = "rescheduled";
118
+ CalendarEventStatus3["COMPLETED"] = "completed";
119
+ return CalendarEventStatus3;
120
+ })(CalendarEventStatus || {});
121
+ var CalendarSyncStatus = /* @__PURE__ */ ((CalendarSyncStatus3) => {
122
+ CalendarSyncStatus3["INTERNAL"] = "internal";
123
+ CalendarSyncStatus3["EXTERNAL"] = "external";
124
+ return CalendarSyncStatus3;
125
+ })(CalendarSyncStatus || {});
126
+ var CalendarEventType = /* @__PURE__ */ ((CalendarEventType2) => {
127
+ CalendarEventType2["APPOINTMENT"] = "appointment";
128
+ CalendarEventType2["BLOCKING"] = "blocking";
129
+ CalendarEventType2["BREAK"] = "break";
130
+ CalendarEventType2["FREE_DAY"] = "free_day";
131
+ CalendarEventType2["OTHER"] = "other";
132
+ return CalendarEventType2;
133
+ })(CalendarEventType || {});
134
+ var CALENDAR_COLLECTION = "calendar";
135
+
111
136
  // src/types/index.ts
112
137
  var UserRole = /* @__PURE__ */ ((UserRole2) => {
113
138
  UserRole2["PATIENT"] = "patient";
@@ -1276,9 +1301,9 @@ var addAllergyUtil = async (db, patientId, data, userRef) => {
1276
1301
  var updateAllergyUtil = async (db, patientId, data, userRef) => {
1277
1302
  const validatedData = updateAllergySchema.parse(data);
1278
1303
  const { allergyIndex, ...updateData } = validatedData;
1279
- const doc14 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1280
- if (!doc14.exists()) throw new Error("Medical info not found");
1281
- const medicalInfo = doc14.data();
1304
+ const doc20 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1305
+ if (!doc20.exists()) throw new Error("Medical info not found");
1306
+ const medicalInfo = doc20.data();
1282
1307
  if (allergyIndex >= medicalInfo.allergies.length) {
1283
1308
  throw new Error("Invalid allergy index");
1284
1309
  }
@@ -1294,9 +1319,9 @@ var updateAllergyUtil = async (db, patientId, data, userRef) => {
1294
1319
  });
1295
1320
  };
1296
1321
  var removeAllergyUtil = async (db, patientId, allergyIndex, userRef) => {
1297
- const doc14 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1298
- if (!doc14.exists()) throw new Error("Medical info not found");
1299
- const medicalInfo = doc14.data();
1322
+ const doc20 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1323
+ if (!doc20.exists()) throw new Error("Medical info not found");
1324
+ const medicalInfo = doc20.data();
1300
1325
  if (allergyIndex >= medicalInfo.allergies.length) {
1301
1326
  throw new Error("Invalid allergy index");
1302
1327
  }
@@ -1321,9 +1346,9 @@ var addBlockingConditionUtil = async (db, patientId, data, userRef) => {
1321
1346
  var updateBlockingConditionUtil = async (db, patientId, data, userRef) => {
1322
1347
  const validatedData = updateBlockingConditionSchema.parse(data);
1323
1348
  const { conditionIndex, ...updateData } = validatedData;
1324
- const doc14 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1325
- if (!doc14.exists()) throw new Error("Medical info not found");
1326
- const medicalInfo = doc14.data();
1349
+ const doc20 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1350
+ if (!doc20.exists()) throw new Error("Medical info not found");
1351
+ const medicalInfo = doc20.data();
1327
1352
  if (conditionIndex >= medicalInfo.blockingConditions.length) {
1328
1353
  throw new Error("Invalid blocking condition index");
1329
1354
  }
@@ -1339,9 +1364,9 @@ var updateBlockingConditionUtil = async (db, patientId, data, userRef) => {
1339
1364
  });
1340
1365
  };
1341
1366
  var removeBlockingConditionUtil = async (db, patientId, conditionIndex, userRef) => {
1342
- const doc14 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1343
- if (!doc14.exists()) throw new Error("Medical info not found");
1344
- const medicalInfo = doc14.data();
1367
+ const doc20 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1368
+ if (!doc20.exists()) throw new Error("Medical info not found");
1369
+ const medicalInfo = doc20.data();
1345
1370
  if (conditionIndex >= medicalInfo.blockingConditions.length) {
1346
1371
  throw new Error("Invalid blocking condition index");
1347
1372
  }
@@ -1366,9 +1391,9 @@ var addContraindicationUtil = async (db, patientId, data, userRef) => {
1366
1391
  var updateContraindicationUtil = async (db, patientId, data, userRef) => {
1367
1392
  const validatedData = updateContraindicationSchema.parse(data);
1368
1393
  const { contraindicationIndex, ...updateData } = validatedData;
1369
- const doc14 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1370
- if (!doc14.exists()) throw new Error("Medical info not found");
1371
- const medicalInfo = doc14.data();
1394
+ const doc20 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1395
+ if (!doc20.exists()) throw new Error("Medical info not found");
1396
+ const medicalInfo = doc20.data();
1372
1397
  if (contraindicationIndex >= medicalInfo.contraindications.length) {
1373
1398
  throw new Error("Invalid contraindication index");
1374
1399
  }
@@ -1384,9 +1409,9 @@ var updateContraindicationUtil = async (db, patientId, data, userRef) => {
1384
1409
  });
1385
1410
  };
1386
1411
  var removeContraindicationUtil = async (db, patientId, contraindicationIndex, userRef) => {
1387
- const doc14 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1388
- if (!doc14.exists()) throw new Error("Medical info not found");
1389
- const medicalInfo = doc14.data();
1412
+ const doc20 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1413
+ if (!doc20.exists()) throw new Error("Medical info not found");
1414
+ const medicalInfo = doc20.data();
1390
1415
  if (contraindicationIndex >= medicalInfo.contraindications.length) {
1391
1416
  throw new Error("Invalid contraindication index");
1392
1417
  }
@@ -1411,9 +1436,9 @@ var addMedicationUtil = async (db, patientId, data, userRef) => {
1411
1436
  var updateMedicationUtil = async (db, patientId, data, userRef) => {
1412
1437
  const validatedData = updateMedicationSchema.parse(data);
1413
1438
  const { medicationIndex, ...updateData } = validatedData;
1414
- const doc14 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1415
- if (!doc14.exists()) throw new Error("Medical info not found");
1416
- const medicalInfo = doc14.data();
1439
+ const doc20 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1440
+ if (!doc20.exists()) throw new Error("Medical info not found");
1441
+ const medicalInfo = doc20.data();
1417
1442
  if (medicationIndex >= medicalInfo.currentMedications.length) {
1418
1443
  throw new Error("Invalid medication index");
1419
1444
  }
@@ -1429,9 +1454,9 @@ var updateMedicationUtil = async (db, patientId, data, userRef) => {
1429
1454
  });
1430
1455
  };
1431
1456
  var removeMedicationUtil = async (db, patientId, medicationIndex, userRef) => {
1432
- const doc14 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1433
- if (!doc14.exists()) throw new Error("Medical info not found");
1434
- const medicalInfo = doc14.data();
1457
+ const doc20 = await getDoc3(getMedicalInfoDocRef(db, patientId));
1458
+ if (!doc20.exists()) throw new Error("Medical info not found");
1459
+ const medicalInfo = doc20.data();
1435
1460
  if (medicationIndex >= medicalInfo.currentMedications.length) {
1436
1461
  throw new Error("Invalid medication index");
1437
1462
  }
@@ -2247,14 +2272,14 @@ var TreatmentBenefit = /* @__PURE__ */ ((TreatmentBenefit2) => {
2247
2272
  })(TreatmentBenefit || {});
2248
2273
 
2249
2274
  // src/backoffice/types/static/pricing.types.ts
2250
- var PricingMeasure = /* @__PURE__ */ ((PricingMeasure2) => {
2251
- PricingMeasure2["PER_ML"] = "per_ml";
2252
- PricingMeasure2["PER_ZONE"] = "per_zone";
2253
- PricingMeasure2["PER_AREA"] = "per_area";
2254
- PricingMeasure2["PER_SESSION"] = "per_session";
2255
- PricingMeasure2["PER_TREATMENT"] = "per_treatment";
2256
- PricingMeasure2["PER_PACKAGE"] = "per_package";
2257
- return PricingMeasure2;
2275
+ var PricingMeasure = /* @__PURE__ */ ((PricingMeasure3) => {
2276
+ PricingMeasure3["PER_ML"] = "per_ml";
2277
+ PricingMeasure3["PER_ZONE"] = "per_zone";
2278
+ PricingMeasure3["PER_AREA"] = "per_area";
2279
+ PricingMeasure3["PER_SESSION"] = "per_session";
2280
+ PricingMeasure3["PER_TREATMENT"] = "per_treatment";
2281
+ PricingMeasure3["PER_PACKAGE"] = "per_package";
2282
+ return PricingMeasure3;
2258
2283
  })(PricingMeasure || {});
2259
2284
  var Currency = /* @__PURE__ */ ((Currency2) => {
2260
2285
  Currency2["EUR"] = "EUR";
@@ -2748,7 +2773,7 @@ async function getClinicAdminsByGroup(db, clinicGroupId) {
2748
2773
  where2("clinicGroupId", "==", clinicGroupId)
2749
2774
  );
2750
2775
  const querySnapshot = await getDocs2(q);
2751
- return querySnapshot.docs.map((doc14) => doc14.data());
2776
+ return querySnapshot.docs.map((doc20) => doc20.data());
2752
2777
  }
2753
2778
  async function updateClinicAdmin(db, adminId, data) {
2754
2779
  const admin = await getClinicAdmin(db, adminId);
@@ -3028,6 +3053,19 @@ import {
3028
3053
 
3029
3054
  // src/types/practitioner/index.ts
3030
3055
  var PRACTITIONERS_COLLECTION = "practitioners";
3056
+ var REGISTER_TOKENS_COLLECTION = "register_tokens";
3057
+ var PractitionerStatus = /* @__PURE__ */ ((PractitionerStatus2) => {
3058
+ PractitionerStatus2["DRAFT"] = "draft";
3059
+ PractitionerStatus2["ACTIVE"] = "active";
3060
+ return PractitionerStatus2;
3061
+ })(PractitionerStatus || {});
3062
+ var PractitionerTokenStatus = /* @__PURE__ */ ((PractitionerTokenStatus2) => {
3063
+ PractitionerTokenStatus2["ACTIVE"] = "active";
3064
+ PractitionerTokenStatus2["USED"] = "used";
3065
+ PractitionerTokenStatus2["EXPIRED"] = "expired";
3066
+ PractitionerTokenStatus2["REVOKED"] = "revoked";
3067
+ return PractitionerTokenStatus2;
3068
+ })(PractitionerTokenStatus || {});
3031
3069
 
3032
3070
  // src/validations/practitioner.schema.ts
3033
3071
  import { z as z10 } from "zod";
@@ -3096,6 +3134,21 @@ var practitionerWorkingHoursSchema = z10.object({
3096
3134
  createdAt: z10.instanceof(Timestamp7),
3097
3135
  updatedAt: z10.instanceof(Timestamp7)
3098
3136
  });
3137
+ var practitionerClinicWorkingHoursSchema = z10.object({
3138
+ clinicId: z10.string().min(1),
3139
+ workingHours: z10.object({
3140
+ monday: timeSlotSchema,
3141
+ tuesday: timeSlotSchema,
3142
+ wednesday: timeSlotSchema,
3143
+ thursday: timeSlotSchema,
3144
+ friday: timeSlotSchema,
3145
+ saturday: timeSlotSchema,
3146
+ sunday: timeSlotSchema
3147
+ }),
3148
+ isActive: z10.boolean(),
3149
+ createdAt: z10.instanceof(Timestamp7),
3150
+ updatedAt: z10.instanceof(Timestamp7)
3151
+ });
3099
3152
  var practitionerReviewSchema = z10.object({
3100
3153
  id: z10.string().min(1),
3101
3154
  practitionerId: z10.string().min(1),
@@ -3121,8 +3174,10 @@ var practitionerSchema = z10.object({
3121
3174
  basicInfo: practitionerBasicInfoSchema,
3122
3175
  certification: practitionerCertificationSchema,
3123
3176
  clinics: z10.array(z10.string()),
3177
+ clinicWorkingHours: z10.array(practitionerClinicWorkingHoursSchema),
3124
3178
  isActive: z10.boolean(),
3125
3179
  isVerified: z10.boolean(),
3180
+ status: z10.nativeEnum(PractitionerStatus),
3126
3181
  createdAt: z10.instanceof(Timestamp7),
3127
3182
  updatedAt: z10.instanceof(Timestamp7)
3128
3183
  });
@@ -3131,8 +3186,37 @@ var createPractitionerSchema = z10.object({
3131
3186
  basicInfo: practitionerBasicInfoSchema,
3132
3187
  certification: practitionerCertificationSchema,
3133
3188
  clinics: z10.array(z10.string()).optional(),
3189
+ clinicWorkingHours: z10.array(practitionerClinicWorkingHoursSchema).optional(),
3134
3190
  isActive: z10.boolean(),
3135
- isVerified: z10.boolean()
3191
+ isVerified: z10.boolean(),
3192
+ status: z10.nativeEnum(PractitionerStatus).optional()
3193
+ });
3194
+ var createDraftPractitionerSchema = z10.object({
3195
+ basicInfo: practitionerBasicInfoSchema,
3196
+ certification: practitionerCertificationSchema,
3197
+ clinics: z10.array(z10.string()).optional(),
3198
+ clinicWorkingHours: z10.array(practitionerClinicWorkingHoursSchema).optional(),
3199
+ isActive: z10.boolean().optional().default(false),
3200
+ isVerified: z10.boolean().optional().default(false)
3201
+ });
3202
+ var practitionerTokenSchema = z10.object({
3203
+ id: z10.string().min(1),
3204
+ token: z10.string().min(6),
3205
+ practitionerId: z10.string().min(1),
3206
+ email: z10.string().email(),
3207
+ clinicId: z10.string().min(1),
3208
+ status: z10.nativeEnum(PractitionerTokenStatus),
3209
+ createdBy: z10.string().min(1),
3210
+ createdAt: z10.instanceof(Timestamp7),
3211
+ expiresAt: z10.instanceof(Timestamp7),
3212
+ usedBy: z10.string().optional(),
3213
+ usedAt: z10.instanceof(Timestamp7).optional()
3214
+ });
3215
+ var createPractitionerTokenSchema = z10.object({
3216
+ practitionerId: z10.string().min(1),
3217
+ email: z10.string().email(),
3218
+ clinicId: z10.string().min(1),
3219
+ expiresAt: z10.date().optional()
3136
3220
  });
3137
3221
 
3138
3222
  // src/services/practitioner/practitioner.service.ts
@@ -3180,8 +3264,10 @@ var PractitionerService = class extends BaseService {
3180
3264
  basicInfo: validatedData.basicInfo,
3181
3265
  certification: validatedData.certification,
3182
3266
  clinics: validatedData.clinics || [],
3267
+ clinicWorkingHours: validatedData.clinicWorkingHours || [],
3183
3268
  isActive: validatedData.isActive,
3184
3269
  isVerified: validatedData.isVerified,
3270
+ status: validatedData.status || "active" /* ACTIVE */,
3185
3271
  createdAt: serverTimestamp9(),
3186
3272
  updatedAt: serverTimestamp9()
3187
3273
  };
@@ -3206,6 +3292,200 @@ var PractitionerService = class extends BaseService {
3206
3292
  throw error;
3207
3293
  }
3208
3294
  }
3295
+ /**
3296
+ * Kreira novi draft profil zdravstvenog radnika bez povezanog korisnika
3297
+ * Koristi se od strane administratora klinike za kreiranje profila i kasnije pozivanje
3298
+ * @param data Podaci za kreiranje draft profila
3299
+ * @param createdBy ID administratora koji kreira profil
3300
+ * @param clinicId ID klinike za koju se kreira profil
3301
+ * @returns Objekt koji sadrži kreirani draft profil i token za registraciju
3302
+ */
3303
+ async createDraftPractitioner(data, createdBy, clinicId) {
3304
+ try {
3305
+ const validatedData = createDraftPractitionerSchema.parse(data);
3306
+ const clinic = await this.getClinicService().getClinic(clinicId);
3307
+ if (!clinic) {
3308
+ throw new Error(`Clinic ${clinicId} not found`);
3309
+ }
3310
+ const clinics = data.clinics || [clinicId];
3311
+ if (data.clinics) {
3312
+ for (const cId of data.clinics) {
3313
+ if (cId !== clinicId) {
3314
+ const otherClinic = await this.getClinicService().getClinic(cId);
3315
+ if (!otherClinic) {
3316
+ throw new Error(`Clinic ${cId} not found`);
3317
+ }
3318
+ }
3319
+ }
3320
+ }
3321
+ const practitionerId = this.generateId();
3322
+ const practitionerData = {
3323
+ id: practitionerId,
3324
+ userRef: "",
3325
+ // Prazno - biće popunjeno kada korisnik kreira nalog
3326
+ basicInfo: validatedData.basicInfo,
3327
+ certification: validatedData.certification,
3328
+ clinics,
3329
+ clinicWorkingHours: validatedData.clinicWorkingHours || [],
3330
+ isActive: validatedData.isActive !== void 0 ? validatedData.isActive : false,
3331
+ isVerified: validatedData.isVerified !== void 0 ? validatedData.isVerified : false,
3332
+ status: "draft" /* DRAFT */,
3333
+ createdAt: serverTimestamp9(),
3334
+ updatedAt: serverTimestamp9()
3335
+ };
3336
+ practitionerSchema.parse({
3337
+ ...practitionerData,
3338
+ userRef: "temp-for-validation",
3339
+ createdAt: Timestamp8.now(),
3340
+ updatedAt: Timestamp8.now()
3341
+ });
3342
+ await setDoc7(
3343
+ doc5(this.db, PRACTITIONERS_COLLECTION, practitionerData.id),
3344
+ practitionerData
3345
+ );
3346
+ const savedPractitioner = await this.getPractitioner(practitionerData.id);
3347
+ if (!savedPractitioner) {
3348
+ throw new Error("Failed to create draft practitioner profile");
3349
+ }
3350
+ const tokenString = this.generateId().slice(0, 6).toUpperCase();
3351
+ const expiration = new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
3352
+ const token = {
3353
+ id: this.generateId(),
3354
+ token: tokenString,
3355
+ practitionerId,
3356
+ email: practitionerData.basicInfo.email,
3357
+ clinicId,
3358
+ status: "active" /* ACTIVE */,
3359
+ createdBy,
3360
+ createdAt: Timestamp8.now(),
3361
+ expiresAt: Timestamp8.fromDate(expiration)
3362
+ };
3363
+ practitionerTokenSchema.parse(token);
3364
+ const tokenPath = `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
3365
+ await setDoc7(doc5(this.db, tokenPath), token);
3366
+ return { practitioner: savedPractitioner, token };
3367
+ } catch (error) {
3368
+ if (error instanceof z11.ZodError) {
3369
+ throw new Error("Invalid practitioner data: " + error.message);
3370
+ }
3371
+ throw error;
3372
+ }
3373
+ }
3374
+ /**
3375
+ * Creates a token for inviting practitioner to claim their profile
3376
+ * @param data Data for creating token
3377
+ * @param createdBy ID of the user creating the token
3378
+ * @returns Created token
3379
+ */
3380
+ async createPractitionerToken(data, createdBy) {
3381
+ try {
3382
+ const validatedData = createPractitionerTokenSchema.parse(data);
3383
+ const practitioner = await this.getPractitioner(
3384
+ validatedData.practitionerId
3385
+ );
3386
+ if (!practitioner) {
3387
+ throw new Error("Practitioner not found");
3388
+ }
3389
+ if (practitioner.status !== "draft" /* DRAFT */) {
3390
+ throw new Error(
3391
+ "Can only create tokens for practitioners in DRAFT status"
3392
+ );
3393
+ }
3394
+ const clinic = await this.getClinicService().getClinic(
3395
+ validatedData.clinicId
3396
+ );
3397
+ if (!clinic) {
3398
+ throw new Error(`Clinic ${validatedData.clinicId} not found`);
3399
+ }
3400
+ if (!practitioner.clinics.includes(validatedData.clinicId)) {
3401
+ throw new Error("Practitioner is not associated with this clinic");
3402
+ }
3403
+ const expiration = validatedData.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
3404
+ const tokenString = this.generateId().slice(0, 6).toUpperCase();
3405
+ const token = {
3406
+ id: this.generateId(),
3407
+ token: tokenString,
3408
+ practitionerId: validatedData.practitionerId,
3409
+ email: validatedData.email,
3410
+ clinicId: validatedData.clinicId,
3411
+ status: "active" /* ACTIVE */,
3412
+ createdBy,
3413
+ createdAt: Timestamp8.now(),
3414
+ expiresAt: Timestamp8.fromDate(expiration)
3415
+ };
3416
+ practitionerTokenSchema.parse(token);
3417
+ const tokenPath = `${PRACTITIONERS_COLLECTION}/${validatedData.practitionerId}/${REGISTER_TOKENS_COLLECTION}/${token.id}`;
3418
+ await setDoc7(doc5(this.db, tokenPath), token);
3419
+ return token;
3420
+ } catch (error) {
3421
+ if (error instanceof z11.ZodError) {
3422
+ throw new Error("Invalid token data: " + error.message);
3423
+ }
3424
+ throw error;
3425
+ }
3426
+ }
3427
+ /**
3428
+ * Gets active tokens for a practitioner
3429
+ * @param practitionerId ID of the practitioner
3430
+ * @returns Array of active tokens
3431
+ */
3432
+ async getPractitionerActiveTokens(practitionerId) {
3433
+ const tokensRef = collection3(
3434
+ this.db,
3435
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
3436
+ );
3437
+ const q = query3(
3438
+ tokensRef,
3439
+ where3("status", "==", "active" /* ACTIVE */),
3440
+ where3("expiresAt", ">", Timestamp8.now())
3441
+ );
3442
+ const querySnapshot = await getDocs3(q);
3443
+ return querySnapshot.docs.map((doc20) => doc20.data());
3444
+ }
3445
+ /**
3446
+ * Gets a token by its string value and validates it
3447
+ * @param tokenString The token string to find
3448
+ * @returns The token if found and valid, null otherwise
3449
+ */
3450
+ async validateToken(tokenString) {
3451
+ const practitionersRef = collection3(this.db, PRACTITIONERS_COLLECTION);
3452
+ const practitionersSnapshot = await getDocs3(practitionersRef);
3453
+ for (const practitionerDoc of practitionersSnapshot.docs) {
3454
+ const practitionerId = practitionerDoc.id;
3455
+ const tokensRef = collection3(
3456
+ this.db,
3457
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
3458
+ );
3459
+ const q = query3(
3460
+ tokensRef,
3461
+ where3("token", "==", tokenString),
3462
+ where3("status", "==", "active" /* ACTIVE */),
3463
+ where3("expiresAt", ">", Timestamp8.now())
3464
+ );
3465
+ const tokenSnapshot = await getDocs3(q);
3466
+ if (!tokenSnapshot.empty) {
3467
+ return tokenSnapshot.docs[0].data();
3468
+ }
3469
+ }
3470
+ return null;
3471
+ }
3472
+ /**
3473
+ * Marks a token as used
3474
+ * @param tokenId ID of the token
3475
+ * @param practitionerId ID of the practitioner
3476
+ * @param userId ID of the user using the token
3477
+ */
3478
+ async markTokenAsUsed(tokenId, practitionerId, userId) {
3479
+ const tokenRef = doc5(
3480
+ this.db,
3481
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${tokenId}`
3482
+ );
3483
+ await updateDoc8(tokenRef, {
3484
+ status: "used" /* USED */,
3485
+ usedBy: userId,
3486
+ usedAt: Timestamp8.now()
3487
+ });
3488
+ }
3209
3489
  /**
3210
3490
  * Dohvata zdravstvenog radnika po ID-u
3211
3491
  */
@@ -3239,10 +3519,23 @@ var PractitionerService = class extends BaseService {
3239
3519
  const q = query3(
3240
3520
  collection3(this.db, PRACTITIONERS_COLLECTION),
3241
3521
  where3("clinics", "array-contains", clinicId),
3242
- where3("isActive", "==", true)
3522
+ where3("isActive", "==", true),
3523
+ where3("status", "==", "active" /* ACTIVE */)
3524
+ );
3525
+ const querySnapshot = await getDocs3(q);
3526
+ return querySnapshot.docs.map((doc20) => doc20.data());
3527
+ }
3528
+ /**
3529
+ * Dohvata sve draft zdravstvene radnike za određenu kliniku
3530
+ */
3531
+ async getDraftPractitionersByClinic(clinicId) {
3532
+ const q = query3(
3533
+ collection3(this.db, PRACTITIONERS_COLLECTION),
3534
+ where3("clinics", "array-contains", clinicId),
3535
+ where3("status", "==", "draft" /* DRAFT */)
3243
3536
  );
3244
3537
  const querySnapshot = await getDocs3(q);
3245
- return querySnapshot.docs.map((doc14) => doc14.data());
3538
+ return querySnapshot.docs.map((doc20) => doc20.data());
3246
3539
  }
3247
3540
  /**
3248
3541
  * Ažurira profil zdravstvenog radnika
@@ -3348,6 +3641,35 @@ var PractitionerService = class extends BaseService {
3348
3641
  }
3349
3642
  await deleteDoc2(doc5(this.db, PRACTITIONERS_COLLECTION, practitionerId));
3350
3643
  }
3644
+ /**
3645
+ * Validates a registration token and claims the associated draft practitioner profile
3646
+ * @param tokenString The token provided by the practitioner
3647
+ * @param userId The ID of the user claiming the profile
3648
+ * @returns The claimed practitioner profile or null if token is invalid
3649
+ */
3650
+ async validateTokenAndClaimProfile(tokenString, userId) {
3651
+ const token = await this.validateToken(tokenString);
3652
+ if (!token) {
3653
+ return null;
3654
+ }
3655
+ const practitioner = await this.getPractitioner(token.practitionerId);
3656
+ if (!practitioner) {
3657
+ return null;
3658
+ }
3659
+ if (practitioner.status !== "draft" /* DRAFT */) {
3660
+ throw new Error("This practitioner profile has already been claimed");
3661
+ }
3662
+ const existingPractitioner = await this.getPractitionerByUserRef(userId);
3663
+ if (existingPractitioner) {
3664
+ throw new Error("User already has a practitioner profile");
3665
+ }
3666
+ const updatedPractitioner = await this.updatePractitioner(practitioner.id, {
3667
+ userRef: userId,
3668
+ status: "active" /* ACTIVE */
3669
+ });
3670
+ await this.markTokenAsUsed(token.id, token.practitionerId, userId);
3671
+ return updatedPractitioner;
3672
+ }
3351
3673
  };
3352
3674
 
3353
3675
  // src/services/user.service.ts
@@ -3522,7 +3844,7 @@ var UserService = class extends BaseService {
3522
3844
  ];
3523
3845
  const q = query4(collection4(this.db, USERS_COLLECTION), ...constraints);
3524
3846
  const querySnapshot = await getDocs4(q);
3525
- const users = querySnapshot.docs.map((doc14) => doc14.data());
3847
+ const users = querySnapshot.docs.map((doc20) => doc20.data());
3526
3848
  return Promise.all(users.map((userData) => userSchema.parse(userData)));
3527
3849
  }
3528
3850
  /**
@@ -3921,7 +4243,7 @@ async function getAllActiveGroups(db) {
3921
4243
  where5("isActive", "==", true)
3922
4244
  );
3923
4245
  const querySnapshot = await getDocs5(q);
3924
- return querySnapshot.docs.map((doc14) => doc14.data());
4246
+ return querySnapshot.docs.map((doc20) => doc20.data());
3925
4247
  }
3926
4248
  async function updateClinicGroup(db, groupId, data, app) {
3927
4249
  console.log("[CLINIC_GROUP] Updating clinic group", { groupId });
@@ -4591,7 +4913,7 @@ async function getClinicsByGroup(db, groupId) {
4591
4913
  where6("isActive", "==", true)
4592
4914
  );
4593
4915
  const querySnapshot = await getDocs6(q);
4594
- return querySnapshot.docs.map((doc14) => doc14.data());
4916
+ return querySnapshot.docs.map((doc20) => doc20.data());
4595
4917
  }
4596
4918
  async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app) {
4597
4919
  console.log("[CLINIC] Starting clinic update", { clinicId, adminId });
@@ -4803,7 +5125,7 @@ async function getClinicsByAdmin(db, adminId, options = {}, clinicAdminService,
4803
5125
  }
4804
5126
  const q = query6(collection6(db, CLINICS_COLLECTION), ...constraints);
4805
5127
  const querySnapshot = await getDocs6(q);
4806
- return querySnapshot.docs.map((doc14) => doc14.data());
5128
+ return querySnapshot.docs.map((doc20) => doc20.data());
4807
5129
  }
4808
5130
  async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGroupService) {
4809
5131
  return getClinicsByAdmin(
@@ -4956,8 +5278,8 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
4956
5278
  }
4957
5279
  const q = query7(collection8(db, CLINICS_COLLECTION), ...constraints);
4958
5280
  const querySnapshot = await getDocs7(q);
4959
- for (const doc14 of querySnapshot.docs) {
4960
- const clinic = doc14.data();
5281
+ for (const doc20 of querySnapshot.docs) {
5282
+ const clinic = doc20.data();
4961
5283
  const distance = distanceBetween(
4962
5284
  [center.latitude, center.longitude],
4963
5285
  [clinic.location.latitude, clinic.location.longitude]
@@ -5831,9 +6153,9 @@ var NotificationService = class extends BaseService {
5831
6153
  orderBy("notificationTime", "desc")
5832
6154
  );
5833
6155
  const querySnapshot = await getDocs9(q);
5834
- return querySnapshot.docs.map((doc14) => ({
5835
- id: doc14.id,
5836
- ...doc14.data()
6156
+ return querySnapshot.docs.map((doc20) => ({
6157
+ id: doc20.id,
6158
+ ...doc20.data()
5837
6159
  }));
5838
6160
  }
5839
6161
  /**
@@ -5847,9 +6169,9 @@ var NotificationService = class extends BaseService {
5847
6169
  orderBy("notificationTime", "desc")
5848
6170
  );
5849
6171
  const querySnapshot = await getDocs9(q);
5850
- return querySnapshot.docs.map((doc14) => ({
5851
- id: doc14.id,
5852
- ...doc14.data()
6172
+ return querySnapshot.docs.map((doc20) => ({
6173
+ id: doc20.id,
6174
+ ...doc20.data()
5853
6175
  }));
5854
6176
  }
5855
6177
  /**
@@ -5921,9 +6243,9 @@ var NotificationService = class extends BaseService {
5921
6243
  orderBy("notificationTime", "desc")
5922
6244
  );
5923
6245
  const querySnapshot = await getDocs9(q);
5924
- return querySnapshot.docs.map((doc14) => ({
5925
- id: doc14.id,
5926
- ...doc14.data()
6246
+ return querySnapshot.docs.map((doc20) => ({
6247
+ id: doc20.id,
6248
+ ...doc20.data()
5927
6249
  }));
5928
6250
  }
5929
6251
  /**
@@ -5936,9 +6258,9 @@ var NotificationService = class extends BaseService {
5936
6258
  orderBy("notificationTime", "desc")
5937
6259
  );
5938
6260
  const querySnapshot = await getDocs9(q);
5939
- return querySnapshot.docs.map((doc14) => ({
5940
- id: doc14.id,
5941
- ...doc14.data()
6261
+ return querySnapshot.docs.map((doc20) => ({
6262
+ id: doc20.id,
6263
+ ...doc20.data()
5942
6264
  }));
5943
6265
  }
5944
6266
  };
@@ -6068,9 +6390,9 @@ var DocumentationTemplateService = class extends BaseService {
6068
6390
  const querySnapshot = await getDocs10(q);
6069
6391
  const templates = [];
6070
6392
  let lastVisible = null;
6071
- querySnapshot.forEach((doc14) => {
6072
- templates.push(doc14.data());
6073
- lastVisible = doc14;
6393
+ querySnapshot.forEach((doc20) => {
6394
+ templates.push(doc20.data());
6395
+ lastVisible = doc20;
6074
6396
  });
6075
6397
  return {
6076
6398
  templates,
@@ -6098,9 +6420,9 @@ var DocumentationTemplateService = class extends BaseService {
6098
6420
  const querySnapshot = await getDocs10(q);
6099
6421
  const templates = [];
6100
6422
  let lastVisible = null;
6101
- querySnapshot.forEach((doc14) => {
6102
- templates.push(doc14.data());
6103
- lastVisible = doc14;
6423
+ querySnapshot.forEach((doc20) => {
6424
+ templates.push(doc20.data());
6425
+ lastVisible = doc20;
6104
6426
  });
6105
6427
  return {
6106
6428
  templates,
@@ -6127,9 +6449,9 @@ var DocumentationTemplateService = class extends BaseService {
6127
6449
  const querySnapshot = await getDocs10(q);
6128
6450
  const templates = [];
6129
6451
  let lastVisible = null;
6130
- querySnapshot.forEach((doc14) => {
6131
- templates.push(doc14.data());
6132
- lastVisible = doc14;
6452
+ querySnapshot.forEach((doc20) => {
6453
+ templates.push(doc20.data());
6454
+ lastVisible = doc20;
6133
6455
  });
6134
6456
  return {
6135
6457
  templates,
@@ -6254,9 +6576,9 @@ var FilledDocumentService = class extends BaseService {
6254
6576
  const querySnapshot = await getDocs11(q);
6255
6577
  const documents = [];
6256
6578
  let lastVisible = null;
6257
- querySnapshot.forEach((doc14) => {
6258
- documents.push(doc14.data());
6259
- lastVisible = doc14;
6579
+ querySnapshot.forEach((doc20) => {
6580
+ documents.push(doc20.data());
6581
+ lastVisible = doc20;
6260
6582
  });
6261
6583
  return {
6262
6584
  documents,
@@ -6283,9 +6605,9 @@ var FilledDocumentService = class extends BaseService {
6283
6605
  const querySnapshot = await getDocs11(q);
6284
6606
  const documents = [];
6285
6607
  let lastVisible = null;
6286
- querySnapshot.forEach((doc14) => {
6287
- documents.push(doc14.data());
6288
- lastVisible = doc14;
6608
+ querySnapshot.forEach((doc20) => {
6609
+ documents.push(doc20.data());
6610
+ lastVisible = doc20;
6289
6611
  });
6290
6612
  return {
6291
6613
  documents,
@@ -6312,9 +6634,9 @@ var FilledDocumentService = class extends BaseService {
6312
6634
  const querySnapshot = await getDocs11(q);
6313
6635
  const documents = [];
6314
6636
  let lastVisible = null;
6315
- querySnapshot.forEach((doc14) => {
6316
- documents.push(doc14.data());
6317
- lastVisible = doc14;
6637
+ querySnapshot.forEach((doc20) => {
6638
+ documents.push(doc20.data());
6639
+ lastVisible = doc20;
6318
6640
  });
6319
6641
  return {
6320
6642
  documents,
@@ -6341,9 +6663,9 @@ var FilledDocumentService = class extends BaseService {
6341
6663
  const querySnapshot = await getDocs11(q);
6342
6664
  const documents = [];
6343
6665
  let lastVisible = null;
6344
- querySnapshot.forEach((doc14) => {
6345
- documents.push(doc14.data());
6346
- lastVisible = doc14;
6666
+ querySnapshot.forEach((doc20) => {
6667
+ documents.push(doc20.data());
6668
+ lastVisible = doc20;
6347
6669
  });
6348
6670
  return {
6349
6671
  documents,
@@ -6370,9 +6692,9 @@ var FilledDocumentService = class extends BaseService {
6370
6692
  const querySnapshot = await getDocs11(q);
6371
6693
  const documents = [];
6372
6694
  let lastVisible = null;
6373
- querySnapshot.forEach((doc14) => {
6374
- documents.push(doc14.data());
6375
- lastVisible = doc14;
6695
+ querySnapshot.forEach((doc20) => {
6696
+ documents.push(doc20.data());
6697
+ lastVisible = doc20;
6376
6698
  });
6377
6699
  return {
6378
6700
  documents,
@@ -6381,55 +6703,2654 @@ var FilledDocumentService = class extends BaseService {
6381
6703
  }
6382
6704
  };
6383
6705
 
6384
- // src/validations/notification.schema.ts
6706
+ // src/services/calendar/calendar-refactored.service.ts
6707
+ import { Timestamp as Timestamp23, serverTimestamp as serverTimestamp18 } from "firebase/firestore";
6708
+
6709
+ // src/types/calendar/synced-calendar.types.ts
6710
+ var SyncedCalendarProvider = /* @__PURE__ */ ((SyncedCalendarProvider3) => {
6711
+ SyncedCalendarProvider3["GOOGLE"] = "google";
6712
+ SyncedCalendarProvider3["OUTLOOK"] = "outlook";
6713
+ SyncedCalendarProvider3["APPLE"] = "apple";
6714
+ return SyncedCalendarProvider3;
6715
+ })(SyncedCalendarProvider || {});
6716
+ var SYNCED_CALENDARS_COLLECTION = "syncedCalendars";
6717
+
6718
+ // src/services/calendar/calendar-refactored.service.ts
6719
+ import {
6720
+ doc as doc19,
6721
+ getDoc as getDoc22,
6722
+ collection as collection17,
6723
+ query as query16,
6724
+ where as where16,
6725
+ getDocs as getDocs16,
6726
+ setDoc as setDoc19,
6727
+ updateDoc as updateDoc20
6728
+ } from "firebase/firestore";
6729
+
6730
+ // src/validations/calendar.schema.ts
6731
+ import { z as z17 } from "zod";
6732
+ import { Timestamp as Timestamp17 } from "firebase/firestore";
6733
+
6734
+ // src/validations/profile-info.schema.ts
6385
6735
  import { z as z16 } from "zod";
6386
- var baseNotificationSchema = z16.object({
6387
- id: z16.string().optional(),
6388
- userId: z16.string(),
6389
- notificationTime: z16.any(),
6736
+ import { Timestamp as Timestamp16 } from "firebase/firestore";
6737
+ var clinicInfoSchema2 = z16.object({
6738
+ id: z16.string(),
6739
+ featuredPhoto: z16.string(),
6740
+ name: z16.string(),
6741
+ description: z16.string(),
6742
+ location: clinicLocationSchema,
6743
+ contactInfo: clinicContactInfoSchema
6744
+ });
6745
+ var practitionerProfileInfoSchema = z16.object({
6746
+ id: z16.string(),
6747
+ practitionerPhoto: z16.string().nullable(),
6748
+ name: z16.string(),
6749
+ email: z16.string().email(),
6750
+ phone: z16.string().nullable(),
6751
+ certification: practitionerCertificationSchema
6752
+ });
6753
+ var patientProfileInfoSchema = z16.object({
6754
+ id: z16.string(),
6755
+ fullName: z16.string(),
6756
+ email: z16.string().email(),
6757
+ phone: z16.string().nullable(),
6758
+ dateOfBirth: z16.instanceof(Timestamp16),
6759
+ gender: z16.nativeEnum(Gender)
6760
+ });
6761
+
6762
+ // src/validations/calendar.schema.ts
6763
+ var MIN_APPOINTMENT_DURATION = 15;
6764
+ var calendarEventTimeSchema = z17.object({
6765
+ start: z17.instanceof(Date).or(z17.instanceof(Timestamp17)),
6766
+ end: z17.instanceof(Date).or(z17.instanceof(Timestamp17))
6767
+ }).refine(
6768
+ (data) => {
6769
+ const startDate = data.start instanceof Timestamp17 ? data.start.toDate() : data.start;
6770
+ const endDate = data.end instanceof Timestamp17 ? data.end.toDate() : data.end;
6771
+ return startDate < endDate;
6772
+ },
6773
+ {
6774
+ message: "End time must be after start time",
6775
+ path: ["end"]
6776
+ }
6777
+ ).refine(
6778
+ (data) => {
6779
+ const startDate = data.start instanceof Timestamp17 ? data.start.toDate() : data.start;
6780
+ return startDate > /* @__PURE__ */ new Date();
6781
+ },
6782
+ {
6783
+ message: "Appointment must be scheduled in the future",
6784
+ path: ["start"]
6785
+ }
6786
+ );
6787
+ var timeSlotSchema2 = z17.object({
6788
+ start: z17.date(),
6789
+ end: z17.date(),
6790
+ isAvailable: z17.boolean()
6791
+ }).refine((data) => data.start < data.end, {
6792
+ message: "End time must be after start time",
6793
+ path: ["end"]
6794
+ });
6795
+ var syncedCalendarEventSchema = z17.object({
6796
+ eventId: z17.string(),
6797
+ syncedCalendarProvider: z17.nativeEnum(SyncedCalendarProvider),
6798
+ syncedAt: z17.instanceof(Date).or(z17.instanceof(Timestamp17))
6799
+ });
6800
+ var procedureInfoSchema = z17.object({
6801
+ name: z17.string(),
6802
+ description: z17.string(),
6803
+ duration: z17.number().min(MIN_APPOINTMENT_DURATION),
6804
+ price: z17.number().min(0),
6805
+ currency: z17.nativeEnum(Currency)
6806
+ });
6807
+ var procedureCategorizationSchema = z17.object({
6808
+ procedureFamily: z17.string(),
6809
+ // Replace with proper enum when available
6810
+ procedureCategory: z17.string(),
6811
+ // Replace with proper enum when available
6812
+ procedureSubcategory: z17.string(),
6813
+ // Replace with proper enum when available
6814
+ procedureTechnology: z17.string(),
6815
+ // Replace with proper enum when available
6816
+ procedureProduct: z17.string()
6817
+ // Replace with proper enum when available
6818
+ });
6819
+ var createAppointmentSchema = z17.object({
6820
+ clinicId: z17.string().min(1, "Clinic ID is required"),
6821
+ doctorId: z17.string().min(1, "Doctor ID is required"),
6822
+ patientId: z17.string().min(1, "Patient ID is required"),
6823
+ procedureId: z17.string().min(1, "Procedure ID is required"),
6824
+ eventLocation: clinicLocationSchema,
6825
+ eventTime: calendarEventTimeSchema,
6826
+ description: z17.string().optional()
6827
+ }).refine(
6828
+ (data) => {
6829
+ return true;
6830
+ },
6831
+ {
6832
+ message: "Invalid appointment parameters"
6833
+ }
6834
+ );
6835
+ var updateAppointmentSchema = z17.object({
6836
+ appointmentId: z17.string().min(1, "Appointment ID is required"),
6837
+ clinicId: z17.string().min(1, "Clinic ID is required"),
6838
+ doctorId: z17.string().min(1, "Doctor ID is required"),
6839
+ patientId: z17.string().min(1, "Patient ID is required"),
6840
+ eventTime: calendarEventTimeSchema.optional(),
6841
+ description: z17.string().optional(),
6842
+ status: z17.nativeEnum(CalendarEventStatus).optional()
6843
+ });
6844
+ var createCalendarEventSchema = z17.object({
6845
+ id: z17.string(),
6846
+ clinicBranchId: z17.string().nullable().optional(),
6847
+ clinicBranchInfo: z17.any().nullable().optional(),
6848
+ practitionerProfileId: z17.string().nullable().optional(),
6849
+ practitionerProfileInfo: practitionerProfileInfoSchema.nullable().optional(),
6850
+ patientProfileId: z17.string().nullable().optional(),
6851
+ patientProfileInfo: patientProfileInfoSchema.nullable().optional(),
6852
+ procedureId: z17.string().nullable().optional(),
6853
+ appointmentId: z17.string().nullable().optional(),
6854
+ syncedCalendarEventId: z17.array(syncedCalendarEventSchema).nullable().optional(),
6855
+ eventName: z17.string().min(1, "Event name is required"),
6856
+ eventLocation: clinicLocationSchema.optional(),
6857
+ eventTime: calendarEventTimeSchema,
6858
+ description: z17.string().optional(),
6859
+ status: z17.nativeEnum(CalendarEventStatus),
6860
+ syncStatus: z17.nativeEnum(CalendarSyncStatus),
6861
+ eventType: z17.nativeEnum(CalendarEventType),
6862
+ createdAt: z17.any(),
6863
+ // FieldValue for server timestamp
6864
+ updatedAt: z17.any()
6865
+ // FieldValue for server timestamp
6866
+ });
6867
+ var updateCalendarEventSchema = z17.object({
6868
+ syncedCalendarEventId: z17.array(syncedCalendarEventSchema).nullable().optional(),
6869
+ appointmentId: z17.string().nullable().optional(),
6870
+ eventName: z17.string().optional(),
6871
+ eventTime: calendarEventTimeSchema.optional(),
6872
+ description: z17.string().optional(),
6873
+ status: z17.nativeEnum(CalendarEventStatus).optional(),
6874
+ syncStatus: z17.nativeEnum(CalendarSyncStatus).optional(),
6875
+ eventType: z17.nativeEnum(CalendarEventType).optional(),
6876
+ updatedAt: z17.any()
6877
+ // FieldValue for server timestamp
6878
+ });
6879
+ var calendarEventSchema = z17.object({
6880
+ id: z17.string(),
6881
+ clinicBranchId: z17.string().nullable().optional(),
6882
+ clinicBranchInfo: z17.any().nullable().optional(),
6883
+ // Will be replaced with proper clinic info schema
6884
+ practitionerProfileId: z17.string().nullable().optional(),
6885
+ practitionerProfileInfo: practitionerProfileInfoSchema.nullable().optional(),
6886
+ patientProfileId: z17.string().nullable().optional(),
6887
+ patientProfileInfo: patientProfileInfoSchema.nullable().optional(),
6888
+ procedureId: z17.string().nullable().optional(),
6889
+ procedureInfo: procedureInfoSchema.nullable().optional(),
6890
+ procedureCategorization: procedureCategorizationSchema.nullable().optional(),
6891
+ appointmentId: z17.string().nullable().optional(),
6892
+ syncedCalendarEventId: z17.array(syncedCalendarEventSchema).nullable().optional(),
6893
+ eventName: z17.string(),
6894
+ eventLocation: clinicLocationSchema.optional(),
6895
+ eventTime: calendarEventTimeSchema,
6896
+ description: z17.string().optional(),
6897
+ status: z17.nativeEnum(CalendarEventStatus),
6898
+ syncStatus: z17.nativeEnum(CalendarSyncStatus),
6899
+ eventType: z17.nativeEnum(CalendarEventType),
6900
+ createdAt: z17.instanceof(Date).or(z17.instanceof(Timestamp17)),
6901
+ updatedAt: z17.instanceof(Date).or(z17.instanceof(Timestamp17))
6902
+ });
6903
+
6904
+ // src/services/calendar/utils/clinic.utils.ts
6905
+ import {
6906
+ collection as collection13,
6907
+ doc as doc15,
6908
+ getDoc as getDoc18,
6909
+ getDocs as getDocs12,
6910
+ setDoc as setDoc15,
6911
+ updateDoc as updateDoc16,
6912
+ deleteDoc as deleteDoc8,
6913
+ query as query12,
6914
+ where as where12,
6915
+ orderBy as orderBy4,
6916
+ Timestamp as Timestamp18,
6917
+ serverTimestamp as serverTimestamp14
6918
+ } from "firebase/firestore";
6919
+
6920
+ // src/services/calendar/utils/docs.utils.ts
6921
+ import { doc as doc14 } from "firebase/firestore";
6922
+ function getPractitionerCalendarEventDocRef(db, practitionerId, eventId) {
6923
+ return doc14(
6924
+ db,
6925
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/${CALENDAR_COLLECTION}/${eventId}`
6926
+ );
6927
+ }
6928
+ function getPatientCalendarEventDocRef(db, patientId, eventId) {
6929
+ return doc14(
6930
+ db,
6931
+ `${PATIENTS_COLLECTION}/${patientId}/${CALENDAR_COLLECTION}/${eventId}`
6932
+ );
6933
+ }
6934
+ function getClinicCalendarEventDocRef(db, clinicId, eventId) {
6935
+ return doc14(
6936
+ db,
6937
+ `${CLINICS_COLLECTION}/${clinicId}/${CALENDAR_COLLECTION}/${eventId}`
6938
+ );
6939
+ }
6940
+ function getPractitionerSyncedCalendarDocRef(db, practitionerId, syncedCalendarId) {
6941
+ return doc14(
6942
+ db,
6943
+ `${PRACTITIONERS_COLLECTION}/${practitionerId}/syncedCalendars/${syncedCalendarId}`
6944
+ );
6945
+ }
6946
+ function getPatientSyncedCalendarDocRef(db, patientId, syncedCalendarId) {
6947
+ return doc14(
6948
+ db,
6949
+ `${PATIENTS_COLLECTION}/${patientId}/syncedCalendars/${syncedCalendarId}`
6950
+ );
6951
+ }
6952
+ function getClinicSyncedCalendarDocRef(db, clinicId, syncedCalendarId) {
6953
+ return doc14(
6954
+ db,
6955
+ `${CLINICS_COLLECTION}/${clinicId}/syncedCalendars/${syncedCalendarId}`
6956
+ );
6957
+ }
6958
+
6959
+ // src/services/calendar/utils/clinic.utils.ts
6960
+ async function createClinicCalendarEventUtil(db, clinicId, eventData, generateId2) {
6961
+ const eventId = generateId2();
6962
+ const eventRef = getClinicCalendarEventDocRef(db, clinicId, eventId);
6963
+ const newEvent = {
6964
+ id: eventId,
6965
+ ...eventData,
6966
+ createdAt: serverTimestamp14(),
6967
+ updatedAt: serverTimestamp14()
6968
+ };
6969
+ await setDoc15(eventRef, newEvent);
6970
+ return {
6971
+ ...newEvent,
6972
+ createdAt: Timestamp18.now(),
6973
+ updatedAt: Timestamp18.now()
6974
+ };
6975
+ }
6976
+ async function updateClinicCalendarEventUtil(db, clinicId, eventId, updateData) {
6977
+ const eventRef = getClinicCalendarEventDocRef(db, clinicId, eventId);
6978
+ const updates = {
6979
+ ...updateData,
6980
+ updatedAt: serverTimestamp14()
6981
+ };
6982
+ await updateDoc16(eventRef, updates);
6983
+ const updatedDoc = await getDoc18(eventRef);
6984
+ if (!updatedDoc.exists()) {
6985
+ throw new Error("Event not found after update");
6986
+ }
6987
+ return updatedDoc.data();
6988
+ }
6989
+ async function checkAutoConfirmAppointmentsUtil(db, clinicId) {
6990
+ const clinicDoc = await getDoc18(doc15(db, `clinics/${clinicId}`));
6991
+ if (!clinicDoc.exists()) {
6992
+ throw new Error(`Clinic with ID ${clinicId} not found`);
6993
+ }
6994
+ const clinicData = clinicDoc.data();
6995
+ const clinicGroupId = clinicData.clinicGroupId;
6996
+ if (!clinicGroupId) {
6997
+ return false;
6998
+ }
6999
+ const clinicGroupDoc = await getDoc18(
7000
+ doc15(db, `${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}`)
7001
+ );
7002
+ if (!clinicGroupDoc.exists()) {
7003
+ return false;
7004
+ }
7005
+ const clinicGroupData = clinicGroupDoc.data();
7006
+ return !!clinicGroupData.autoConfirmAppointments;
7007
+ }
7008
+
7009
+ // src/services/calendar/utils/patient.utils.ts
7010
+ import {
7011
+ collection as collection14,
7012
+ getDoc as getDoc19,
7013
+ getDocs as getDocs13,
7014
+ setDoc as setDoc16,
7015
+ updateDoc as updateDoc17,
7016
+ deleteDoc as deleteDoc9,
7017
+ query as query13,
7018
+ where as where13,
7019
+ orderBy as orderBy5,
7020
+ Timestamp as Timestamp19,
7021
+ serverTimestamp as serverTimestamp15
7022
+ } from "firebase/firestore";
7023
+ async function createPatientCalendarEventUtil(db, patientId, eventData, generateId2) {
7024
+ const eventId = generateId2();
7025
+ const eventRef = getPatientCalendarEventDocRef(db, patientId, eventId);
7026
+ const newEvent = {
7027
+ id: eventId,
7028
+ ...eventData,
7029
+ createdAt: serverTimestamp15(),
7030
+ updatedAt: serverTimestamp15()
7031
+ };
7032
+ await setDoc16(eventRef, newEvent);
7033
+ return {
7034
+ ...newEvent,
7035
+ createdAt: Timestamp19.now(),
7036
+ updatedAt: Timestamp19.now()
7037
+ };
7038
+ }
7039
+ async function updatePatientCalendarEventUtil(db, patientId, eventId, updateData) {
7040
+ const eventRef = getPatientCalendarEventDocRef(db, patientId, eventId);
7041
+ const updates = {
7042
+ ...updateData,
7043
+ updatedAt: serverTimestamp15()
7044
+ };
7045
+ await updateDoc17(eventRef, updates);
7046
+ const updatedDoc = await getDoc19(eventRef);
7047
+ if (!updatedDoc.exists()) {
7048
+ throw new Error("Event not found after update");
7049
+ }
7050
+ return updatedDoc.data();
7051
+ }
7052
+
7053
+ // src/services/calendar/utils/practitioner.utils.ts
7054
+ import {
7055
+ collection as collection15,
7056
+ getDoc as getDoc20,
7057
+ getDocs as getDocs14,
7058
+ setDoc as setDoc17,
7059
+ updateDoc as updateDoc18,
7060
+ deleteDoc as deleteDoc10,
7061
+ query as query14,
7062
+ where as where14,
7063
+ orderBy as orderBy6,
7064
+ Timestamp as Timestamp20,
7065
+ serverTimestamp as serverTimestamp16
7066
+ } from "firebase/firestore";
7067
+ async function createPractitionerCalendarEventUtil(db, practitionerId, eventData, generateId2) {
7068
+ const eventId = generateId2();
7069
+ const eventRef = getPractitionerCalendarEventDocRef(
7070
+ db,
7071
+ practitionerId,
7072
+ eventId
7073
+ );
7074
+ const newEvent = {
7075
+ id: eventId,
7076
+ ...eventData,
7077
+ createdAt: serverTimestamp16(),
7078
+ updatedAt: serverTimestamp16()
7079
+ };
7080
+ await setDoc17(eventRef, newEvent);
7081
+ return {
7082
+ ...newEvent,
7083
+ createdAt: Timestamp20.now(),
7084
+ updatedAt: Timestamp20.now()
7085
+ };
7086
+ }
7087
+ async function updatePractitionerCalendarEventUtil(db, practitionerId, eventId, updateData) {
7088
+ const eventRef = getPractitionerCalendarEventDocRef(
7089
+ db,
7090
+ practitionerId,
7091
+ eventId
7092
+ );
7093
+ const updates = {
7094
+ ...updateData,
7095
+ updatedAt: serverTimestamp16()
7096
+ };
7097
+ await updateDoc18(eventRef, updates);
7098
+ const updatedDoc = await getDoc20(eventRef);
7099
+ if (!updatedDoc.exists()) {
7100
+ throw new Error("Event not found after update");
7101
+ }
7102
+ return updatedDoc.data();
7103
+ }
7104
+
7105
+ // src/services/calendar/utils/appointment.utils.ts
7106
+ async function createAppointmentUtil(db, clinicId, practitionerId, patientId, eventData, generateId2) {
7107
+ const eventId = generateId2();
7108
+ const autoConfirm = await checkAutoConfirmAppointmentsUtil(db, clinicId);
7109
+ const initialStatus = autoConfirm ? "confirmed" /* CONFIRMED */ : "pending" /* PENDING */;
7110
+ const appointmentData = {
7111
+ ...eventData,
7112
+ clinicBranchId: clinicId,
7113
+ practitionerProfileId: practitionerId,
7114
+ patientProfileId: patientId,
7115
+ eventType: "appointment" /* APPOINTMENT */,
7116
+ status: eventData.status || initialStatus
7117
+ };
7118
+ const clinicPromise = createClinicCalendarEventUtil(
7119
+ db,
7120
+ clinicId,
7121
+ appointmentData,
7122
+ () => eventId
7123
+ // Use the same ID for all calendars
7124
+ );
7125
+ const practitionerPromise = createPractitionerCalendarEventUtil(
7126
+ db,
7127
+ practitionerId,
7128
+ appointmentData,
7129
+ () => eventId
7130
+ // Use the same ID for all calendars
7131
+ );
7132
+ const patientPromise = createPatientCalendarEventUtil(
7133
+ db,
7134
+ patientId,
7135
+ appointmentData,
7136
+ () => eventId
7137
+ // Use the same ID for all calendars
7138
+ );
7139
+ const [clinicEvent] = await Promise.all([
7140
+ clinicPromise,
7141
+ practitionerPromise,
7142
+ patientPromise
7143
+ ]);
7144
+ return clinicEvent;
7145
+ }
7146
+ async function updateAppointmentUtil(db, clinicId, practitionerId, patientId, eventId, updateData) {
7147
+ const clinicPromise = updateClinicCalendarEventUtil(
7148
+ db,
7149
+ clinicId,
7150
+ eventId,
7151
+ updateData
7152
+ );
7153
+ const practitionerPromise = updatePractitionerCalendarEventUtil(
7154
+ db,
7155
+ practitionerId,
7156
+ eventId,
7157
+ updateData
7158
+ );
7159
+ const patientPromise = updatePatientCalendarEventUtil(
7160
+ db,
7161
+ patientId,
7162
+ eventId,
7163
+ updateData
7164
+ );
7165
+ const [clinicEvent] = await Promise.all([
7166
+ clinicPromise,
7167
+ practitionerPromise,
7168
+ patientPromise
7169
+ ]);
7170
+ return clinicEvent;
7171
+ }
7172
+
7173
+ // src/services/calendar/utils/synced-calendar.utils.ts
7174
+ import {
7175
+ collection as collection16,
7176
+ getDoc as getDoc21,
7177
+ getDocs as getDocs15,
7178
+ setDoc as setDoc18,
7179
+ updateDoc as updateDoc19,
7180
+ deleteDoc as deleteDoc11,
7181
+ query as query15,
7182
+ orderBy as orderBy7,
7183
+ Timestamp as Timestamp21,
7184
+ serverTimestamp as serverTimestamp17
7185
+ } from "firebase/firestore";
7186
+ async function createPractitionerSyncedCalendarUtil(db, practitionerId, calendarData, generateId2) {
7187
+ const calendarId = generateId2();
7188
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
7189
+ db,
7190
+ practitionerId,
7191
+ calendarId
7192
+ );
7193
+ const newCalendar = {
7194
+ id: calendarId,
7195
+ ...calendarData,
7196
+ createdAt: serverTimestamp17(),
7197
+ updatedAt: serverTimestamp17()
7198
+ };
7199
+ await setDoc18(calendarRef, newCalendar);
7200
+ return {
7201
+ ...newCalendar,
7202
+ createdAt: Timestamp21.now(),
7203
+ updatedAt: Timestamp21.now()
7204
+ };
7205
+ }
7206
+ async function createPatientSyncedCalendarUtil(db, patientId, calendarData, generateId2) {
7207
+ const calendarId = generateId2();
7208
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
7209
+ const newCalendar = {
7210
+ id: calendarId,
7211
+ ...calendarData,
7212
+ createdAt: serverTimestamp17(),
7213
+ updatedAt: serverTimestamp17()
7214
+ };
7215
+ await setDoc18(calendarRef, newCalendar);
7216
+ return {
7217
+ ...newCalendar,
7218
+ createdAt: Timestamp21.now(),
7219
+ updatedAt: Timestamp21.now()
7220
+ };
7221
+ }
7222
+ async function createClinicSyncedCalendarUtil(db, clinicId, calendarData, generateId2) {
7223
+ const calendarId = generateId2();
7224
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
7225
+ const newCalendar = {
7226
+ id: calendarId,
7227
+ ...calendarData,
7228
+ createdAt: serverTimestamp17(),
7229
+ updatedAt: serverTimestamp17()
7230
+ };
7231
+ await setDoc18(calendarRef, newCalendar);
7232
+ return {
7233
+ ...newCalendar,
7234
+ createdAt: Timestamp21.now(),
7235
+ updatedAt: Timestamp21.now()
7236
+ };
7237
+ }
7238
+ async function getPractitionerSyncedCalendarUtil(db, practitionerId, calendarId) {
7239
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
7240
+ db,
7241
+ practitionerId,
7242
+ calendarId
7243
+ );
7244
+ const calendarDoc = await getDoc21(calendarRef);
7245
+ if (!calendarDoc.exists()) {
7246
+ return null;
7247
+ }
7248
+ return calendarDoc.data();
7249
+ }
7250
+ async function getPractitionerSyncedCalendarsUtil(db, practitionerId) {
7251
+ const calendarsRef = collection16(
7252
+ db,
7253
+ `practitioners/${practitionerId}/${SYNCED_CALENDARS_COLLECTION}`
7254
+ );
7255
+ const q = query15(calendarsRef, orderBy7("createdAt", "desc"));
7256
+ const querySnapshot = await getDocs15(q);
7257
+ return querySnapshot.docs.map((doc20) => doc20.data());
7258
+ }
7259
+ async function getPatientSyncedCalendarUtil(db, patientId, calendarId) {
7260
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
7261
+ const calendarDoc = await getDoc21(calendarRef);
7262
+ if (!calendarDoc.exists()) {
7263
+ return null;
7264
+ }
7265
+ return calendarDoc.data();
7266
+ }
7267
+ async function getPatientSyncedCalendarsUtil(db, patientId) {
7268
+ const calendarsRef = collection16(
7269
+ db,
7270
+ `patients/${patientId}/${SYNCED_CALENDARS_COLLECTION}`
7271
+ );
7272
+ const q = query15(calendarsRef, orderBy7("createdAt", "desc"));
7273
+ const querySnapshot = await getDocs15(q);
7274
+ return querySnapshot.docs.map((doc20) => doc20.data());
7275
+ }
7276
+ async function getClinicSyncedCalendarUtil(db, clinicId, calendarId) {
7277
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
7278
+ const calendarDoc = await getDoc21(calendarRef);
7279
+ if (!calendarDoc.exists()) {
7280
+ return null;
7281
+ }
7282
+ return calendarDoc.data();
7283
+ }
7284
+ async function getClinicSyncedCalendarsUtil(db, clinicId) {
7285
+ const calendarsRef = collection16(
7286
+ db,
7287
+ `clinics/${clinicId}/${SYNCED_CALENDARS_COLLECTION}`
7288
+ );
7289
+ const q = query15(calendarsRef, orderBy7("createdAt", "desc"));
7290
+ const querySnapshot = await getDocs15(q);
7291
+ return querySnapshot.docs.map((doc20) => doc20.data());
7292
+ }
7293
+ async function updatePractitionerSyncedCalendarUtil(db, practitionerId, calendarId, updateData) {
7294
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
7295
+ db,
7296
+ practitionerId,
7297
+ calendarId
7298
+ );
7299
+ const updates = {
7300
+ ...updateData,
7301
+ updatedAt: serverTimestamp17()
7302
+ };
7303
+ await updateDoc19(calendarRef, updates);
7304
+ const updatedDoc = await getDoc21(calendarRef);
7305
+ if (!updatedDoc.exists()) {
7306
+ throw new Error("Synced calendar not found after update");
7307
+ }
7308
+ return updatedDoc.data();
7309
+ }
7310
+ async function updatePatientSyncedCalendarUtil(db, patientId, calendarId, updateData) {
7311
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
7312
+ const updates = {
7313
+ ...updateData,
7314
+ updatedAt: serverTimestamp17()
7315
+ };
7316
+ await updateDoc19(calendarRef, updates);
7317
+ const updatedDoc = await getDoc21(calendarRef);
7318
+ if (!updatedDoc.exists()) {
7319
+ throw new Error("Synced calendar not found after update");
7320
+ }
7321
+ return updatedDoc.data();
7322
+ }
7323
+ async function updateClinicSyncedCalendarUtil(db, clinicId, calendarId, updateData) {
7324
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
7325
+ const updates = {
7326
+ ...updateData,
7327
+ updatedAt: serverTimestamp17()
7328
+ };
7329
+ await updateDoc19(calendarRef, updates);
7330
+ const updatedDoc = await getDoc21(calendarRef);
7331
+ if (!updatedDoc.exists()) {
7332
+ throw new Error("Synced calendar not found after update");
7333
+ }
7334
+ return updatedDoc.data();
7335
+ }
7336
+ async function deletePractitionerSyncedCalendarUtil(db, practitionerId, calendarId) {
7337
+ const calendarRef = getPractitionerSyncedCalendarDocRef(
7338
+ db,
7339
+ practitionerId,
7340
+ calendarId
7341
+ );
7342
+ await deleteDoc11(calendarRef);
7343
+ }
7344
+ async function deletePatientSyncedCalendarUtil(db, patientId, calendarId) {
7345
+ const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
7346
+ await deleteDoc11(calendarRef);
7347
+ }
7348
+ async function deleteClinicSyncedCalendarUtil(db, clinicId, calendarId) {
7349
+ const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
7350
+ await deleteDoc11(calendarRef);
7351
+ }
7352
+ async function updateLastSyncedTimestampUtil(db, entityType, entityId, calendarId) {
7353
+ const updateData = {
7354
+ lastSyncedAt: Timestamp21.now()
7355
+ };
7356
+ switch (entityType) {
7357
+ case "practitioner":
7358
+ return updatePractitionerSyncedCalendarUtil(
7359
+ db,
7360
+ entityId,
7361
+ calendarId,
7362
+ updateData
7363
+ );
7364
+ case "patient":
7365
+ return updatePatientSyncedCalendarUtil(
7366
+ db,
7367
+ entityId,
7368
+ calendarId,
7369
+ updateData
7370
+ );
7371
+ case "clinic":
7372
+ return updateClinicSyncedCalendarUtil(
7373
+ db,
7374
+ entityId,
7375
+ calendarId,
7376
+ updateData
7377
+ );
7378
+ default:
7379
+ throw new Error(`Invalid entity type: ${entityType}`);
7380
+ }
7381
+ }
7382
+
7383
+ // src/services/calendar/utils/google-calendar.utils.ts
7384
+ import { Timestamp as Timestamp22 } from "firebase/firestore";
7385
+ var GOOGLE_CALENDAR_API_URL = "https://www.googleapis.com/calendar/v3";
7386
+ var GOOGLE_OAUTH_URL = "https://oauth2.googleapis.com/token";
7387
+ var CLIENT_ID = "your-client-id";
7388
+ var CLIENT_SECRET = "your-client-secret";
7389
+ var REDIRECT_URI = "your-redirect-uri";
7390
+ async function makeRequest(method, url, headers, data, params) {
7391
+ const queryParams = params ? "?" + Object.entries(params).map(
7392
+ ([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
7393
+ ).join("&") : "";
7394
+ const finalUrl = url + queryParams;
7395
+ const options = {
7396
+ method,
7397
+ headers,
7398
+ body: data ? JSON.stringify(data) : void 0
7399
+ };
7400
+ const response = await fetch(finalUrl, options);
7401
+ if (!response.ok) {
7402
+ const error = new Error(
7403
+ `Request failed with status ${response.status}`
7404
+ );
7405
+ error.response = response;
7406
+ throw error;
7407
+ }
7408
+ return response.json();
7409
+ }
7410
+ async function authenticateWithGoogleCalendarUtil(authCode) {
7411
+ try {
7412
+ const data = {
7413
+ code: authCode,
7414
+ client_id: CLIENT_ID,
7415
+ client_secret: CLIENT_SECRET,
7416
+ redirect_uri: REDIRECT_URI,
7417
+ grant_type: "authorization_code"
7418
+ };
7419
+ const response = await makeRequest(
7420
+ "post",
7421
+ GOOGLE_OAUTH_URL,
7422
+ { "Content-Type": "application/json" },
7423
+ data
7424
+ );
7425
+ return {
7426
+ accessToken: response.access_token,
7427
+ refreshToken: response.refresh_token,
7428
+ expiresIn: response.expires_in
7429
+ };
7430
+ } catch (error) {
7431
+ const apiError = error;
7432
+ console.error(
7433
+ "Error authenticating with Google Calendar:",
7434
+ apiError.message || "Unknown error"
7435
+ );
7436
+ throw new Error(
7437
+ `Failed to authenticate with Google Calendar: ${apiError.message || "Unknown error"}`
7438
+ );
7439
+ }
7440
+ }
7441
+ async function refreshGoogleCalendarTokenUtil(refreshToken) {
7442
+ try {
7443
+ const data = {
7444
+ refresh_token: refreshToken,
7445
+ client_id: CLIENT_ID,
7446
+ client_secret: CLIENT_SECRET,
7447
+ grant_type: "refresh_token"
7448
+ };
7449
+ const response = await makeRequest(
7450
+ "post",
7451
+ GOOGLE_OAUTH_URL,
7452
+ { "Content-Type": "application/json" },
7453
+ data
7454
+ );
7455
+ return {
7456
+ accessToken: response.access_token,
7457
+ expiresIn: response.expires_in
7458
+ };
7459
+ } catch (error) {
7460
+ const apiError = error;
7461
+ console.error(
7462
+ "Error refreshing Google Calendar token:",
7463
+ apiError.message || "Unknown error"
7464
+ );
7465
+ throw new Error(
7466
+ `Failed to refresh Google Calendar token: ${apiError.message || "Unknown error"}`
7467
+ );
7468
+ }
7469
+ }
7470
+ async function listGoogleCalendarsUtil(accessToken) {
7471
+ try {
7472
+ const response = await makeRequest(
7473
+ "get",
7474
+ `${GOOGLE_CALENDAR_API_URL}/users/me/calendarList`,
7475
+ { Authorization: `Bearer ${accessToken}` }
7476
+ );
7477
+ return response.items.map((calendar) => ({
7478
+ id: calendar.id,
7479
+ name: calendar.summary
7480
+ }));
7481
+ } catch (error) {
7482
+ const apiError = error;
7483
+ console.error(
7484
+ "Error listing Google Calendars:",
7485
+ apiError.message || "Unknown error"
7486
+ );
7487
+ throw new Error(
7488
+ `Failed to list Google Calendars: ${apiError.message || "Unknown error"}`
7489
+ );
7490
+ }
7491
+ }
7492
+ async function ensureValidToken(db, entityType, entityId, syncedCalendar) {
7493
+ const expiryTime = syncedCalendar.tokenExpiry.toDate();
7494
+ const now = /* @__PURE__ */ new Date();
7495
+ const fiveMinutesFromNow = new Date(now.getTime() + 5 * 60 * 1e3);
7496
+ if (expiryTime < fiveMinutesFromNow) {
7497
+ const { accessToken, expiresIn } = await refreshGoogleCalendarTokenUtil(
7498
+ syncedCalendar.refreshToken
7499
+ );
7500
+ const tokenExpiry = /* @__PURE__ */ new Date();
7501
+ tokenExpiry.setSeconds(tokenExpiry.getSeconds() + expiresIn);
7502
+ const updateData = {
7503
+ accessToken,
7504
+ tokenExpiry: Timestamp22.fromDate(tokenExpiry)
7505
+ };
7506
+ switch (entityType) {
7507
+ case "practitioner":
7508
+ await updatePractitionerSyncedCalendarUtil(
7509
+ db,
7510
+ entityId,
7511
+ syncedCalendar.id,
7512
+ updateData
7513
+ );
7514
+ break;
7515
+ case "patient":
7516
+ await updatePatientSyncedCalendarUtil(
7517
+ db,
7518
+ entityId,
7519
+ syncedCalendar.id,
7520
+ updateData
7521
+ );
7522
+ break;
7523
+ case "clinic":
7524
+ await updateClinicSyncedCalendarUtil(
7525
+ db,
7526
+ entityId,
7527
+ syncedCalendar.id,
7528
+ updateData
7529
+ );
7530
+ break;
7531
+ }
7532
+ return accessToken;
7533
+ }
7534
+ return syncedCalendar.accessToken;
7535
+ }
7536
+ async function syncEventsToGoogleCalendarUtil(db, entityType, entityId, syncedCalendar, events, existingSyncId) {
7537
+ var _a, _b;
7538
+ try {
7539
+ const { accessToken } = await refreshGoogleCalendarTokenUtil(
7540
+ syncedCalendar.refreshToken
7541
+ );
7542
+ let syncedCount = 0;
7543
+ const errors = [];
7544
+ const eventIds = [];
7545
+ for (const event of events) {
7546
+ try {
7547
+ if (event.syncStatus === "external" /* EXTERNAL */) {
7548
+ continue;
7549
+ }
7550
+ if (entityType === "practitioner" && event.status !== "confirmed" /* CONFIRMED */) {
7551
+ continue;
7552
+ }
7553
+ if (entityType === "patient" && (event.status === "canceled" /* CANCELED */ || event.status === "rejected" /* REJECTED */)) {
7554
+ continue;
7555
+ }
7556
+ if (entityType === "clinic") {
7557
+ continue;
7558
+ }
7559
+ const googleEvent = convertCalendarEventToGoogleEventUtil(event);
7560
+ const headers = {
7561
+ Authorization: `Bearer ${accessToken}`,
7562
+ "Content-Type": "application/json"
7563
+ };
7564
+ let responseId = "";
7565
+ if (existingSyncId) {
7566
+ const response = await makeRequest(
7567
+ "put",
7568
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${existingSyncId}`,
7569
+ headers,
7570
+ googleEvent
7571
+ );
7572
+ responseId = response.id;
7573
+ } else {
7574
+ const existingSync = (_a = event.syncedCalendarEventId) == null ? void 0 : _a.find(
7575
+ (sync) => sync.syncedCalendarProvider === "google" /* GOOGLE */ && // We should check if this is the same calendar we're syncing with, but that information isn't stored
7576
+ // For now, we'll just use the first Google Calendar sync ID
7577
+ sync.syncedCalendarProvider === syncedCalendar.provider
7578
+ );
7579
+ if (existingSync) {
7580
+ const response = await makeRequest(
7581
+ "put",
7582
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${existingSync.eventId}`,
7583
+ headers,
7584
+ googleEvent
7585
+ );
7586
+ responseId = response.id;
7587
+ } else {
7588
+ const response = await makeRequest(
7589
+ "post",
7590
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events`,
7591
+ headers,
7592
+ googleEvent
7593
+ );
7594
+ responseId = response.id;
7595
+ }
7596
+ }
7597
+ if (responseId) {
7598
+ eventIds.push(responseId);
7599
+ syncedCount++;
7600
+ }
7601
+ } catch (error) {
7602
+ const apiError = error;
7603
+ errors.push({
7604
+ eventId: event.id,
7605
+ error: apiError.message || "Unknown error",
7606
+ status: (_b = apiError.response) == null ? void 0 : _b.status
7607
+ });
7608
+ }
7609
+ }
7610
+ await updateLastSyncedTimestampUtil(
7611
+ db,
7612
+ entityType,
7613
+ entityId,
7614
+ syncedCalendar.id
7615
+ );
7616
+ return {
7617
+ success: errors.length === 0,
7618
+ syncedEvents: syncedCount,
7619
+ errors,
7620
+ eventIds
7621
+ };
7622
+ } catch (error) {
7623
+ console.error("Error syncing with Google Calendar:", error);
7624
+ return {
7625
+ success: false,
7626
+ syncedEvents: 0,
7627
+ errors: [{ error: error.message || "Unknown error" }],
7628
+ eventIds: []
7629
+ };
7630
+ }
7631
+ }
7632
+ async function fetchEventsFromGoogleCalendarUtil(db, entityType, entityId, syncedCalendar, startDate, endDate) {
7633
+ try {
7634
+ const accessToken = await ensureValidToken(
7635
+ db,
7636
+ entityType,
7637
+ entityId,
7638
+ syncedCalendar
7639
+ );
7640
+ const timeMin = startDate.toISOString();
7641
+ const timeMax = endDate.toISOString();
7642
+ const response = await makeRequest(
7643
+ "get",
7644
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events`,
7645
+ { Authorization: `Bearer ${accessToken}` },
7646
+ void 0,
7647
+ {
7648
+ timeMin,
7649
+ timeMax,
7650
+ singleEvents: "true",
7651
+ orderBy: "startTime"
7652
+ }
7653
+ );
7654
+ await updateLastSyncedTimestampUtil(
7655
+ db,
7656
+ entityType,
7657
+ entityId,
7658
+ syncedCalendar.id
7659
+ );
7660
+ return response.items;
7661
+ } catch (error) {
7662
+ const apiError = error;
7663
+ console.error(
7664
+ "Error fetching events from Google Calendar:",
7665
+ apiError.message || "Unknown error"
7666
+ );
7667
+ throw new Error(
7668
+ `Failed to fetch events from Google Calendar: ${apiError.message || "Unknown error"}`
7669
+ );
7670
+ }
7671
+ }
7672
+ function convertGoogleEventToCalendarEventUtil(googleEvent, entityId, entityType) {
7673
+ const start = googleEvent.start.dateTime ? new Date(googleEvent.start.dateTime) : new Date(googleEvent.start.date);
7674
+ const end = googleEvent.end.dateTime ? new Date(googleEvent.end.dateTime) : new Date(googleEvent.end.date);
7675
+ const calendarEvent = {
7676
+ eventName: googleEvent.summary || "External Event",
7677
+ eventLocation: googleEvent.location,
7678
+ eventTime: {
7679
+ start: Timestamp22.fromDate(start),
7680
+ end: Timestamp22.fromDate(end)
7681
+ },
7682
+ description: googleEvent.description || "",
7683
+ // External events are always set as CONFIRMED - status updates will happen externally
7684
+ status: "confirmed" /* CONFIRMED */,
7685
+ // All external events are marked as EXTERNAL to indicate they originated outside our system
7686
+ syncStatus: "external" /* EXTERNAL */,
7687
+ // All external events are treated as BLOCKING events
7688
+ eventType: "blocking" /* BLOCKING */,
7689
+ // Store the original Google Calendar event ID
7690
+ syncedCalendarEventId: [
7691
+ {
7692
+ eventId: googleEvent.id,
7693
+ syncedCalendarProvider: "google" /* GOOGLE */,
7694
+ syncedAt: Timestamp22.now()
7695
+ }
7696
+ ]
7697
+ };
7698
+ switch (entityType) {
7699
+ case "practitioner":
7700
+ calendarEvent.practitionerProfileId = entityId;
7701
+ break;
7702
+ case "patient":
7703
+ calendarEvent.patientProfileId = entityId;
7704
+ break;
7705
+ case "clinic":
7706
+ calendarEvent.clinicBranchId = entityId;
7707
+ break;
7708
+ }
7709
+ return calendarEvent;
7710
+ }
7711
+ function convertCalendarEventToGoogleEventUtil(calendarEvent) {
7712
+ const googleEvent = {
7713
+ summary: calendarEvent.eventName,
7714
+ location: calendarEvent.eventLocation,
7715
+ description: calendarEvent.description,
7716
+ start: {
7717
+ dateTime: calendarEvent.eventTime.start.toDate().toISOString(),
7718
+ timeZone: "UTC"
7719
+ },
7720
+ end: {
7721
+ dateTime: calendarEvent.eventTime.end.toDate().toISOString(),
7722
+ timeZone: "UTC"
7723
+ },
7724
+ // Add reminders
7725
+ reminders: {
7726
+ useDefault: false,
7727
+ overrides: [
7728
+ { method: "email", minutes: 24 * 60 },
7729
+ // 1 day before
7730
+ { method: "popup", minutes: 30 }
7731
+ // 30 minutes before
7732
+ ]
7733
+ }
7734
+ };
7735
+ switch (calendarEvent.status) {
7736
+ case "confirmed" /* CONFIRMED */:
7737
+ googleEvent.status = "confirmed";
7738
+ break;
7739
+ case "canceled" /* CANCELED */:
7740
+ googleEvent.status = "cancelled";
7741
+ break;
7742
+ case "pending" /* PENDING */:
7743
+ googleEvent.status = "tentative";
7744
+ break;
7745
+ default:
7746
+ googleEvent.status = "confirmed";
7747
+ }
7748
+ if (calendarEvent.eventType === "appointment" /* APPOINTMENT */) {
7749
+ googleEvent.attendees = [];
7750
+ if (calendarEvent.practitionerProfileId) {
7751
+ googleEvent.attendees.push({
7752
+ email: "practitioner@example.com",
7753
+ // This would be fetched from the practitioner profile
7754
+ displayName: "Dr. Practitioner",
7755
+ // This would be fetched from the practitioner profile
7756
+ responseStatus: "accepted"
7757
+ });
7758
+ }
7759
+ if (calendarEvent.patientProfileId) {
7760
+ googleEvent.attendees.push({
7761
+ email: "patient@example.com",
7762
+ // This would be fetched from the patient profile
7763
+ displayName: "Patient",
7764
+ // This would be fetched from the patient profile
7765
+ responseStatus: "needsAction"
7766
+ });
7767
+ }
7768
+ }
7769
+ return googleEvent;
7770
+ }
7771
+ async function deleteGoogleCalendarEventUtil(db, entityType, entityId, syncedCalendar, eventId) {
7772
+ try {
7773
+ const accessToken = await ensureValidToken(
7774
+ db,
7775
+ entityType,
7776
+ entityId,
7777
+ syncedCalendar
7778
+ );
7779
+ await makeRequest(
7780
+ "delete",
7781
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${eventId}`,
7782
+ { Authorization: `Bearer ${accessToken}` }
7783
+ );
7784
+ return true;
7785
+ } catch (error) {
7786
+ const apiError = error;
7787
+ console.error(
7788
+ "Error deleting event from Google Calendar:",
7789
+ apiError.message || "Unknown error"
7790
+ );
7791
+ throw new Error(
7792
+ `Failed to delete event from Google Calendar: ${apiError.message || "Unknown error"}`
7793
+ );
7794
+ }
7795
+ }
7796
+ function getGoogleCalendarOAuthUrlUtil(scopes = ["https://www.googleapis.com/auth/calendar"]) {
7797
+ const scopeString = encodeURIComponent(scopes.join(" "));
7798
+ return `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(
7799
+ REDIRECT_URI
7800
+ )}&response_type=code&scope=${scopeString}&access_type=offline&prompt=consent`;
7801
+ }
7802
+
7803
+ // src/services/calendar/synced-calendars.service.ts
7804
+ var SyncedCalendarsService = class extends BaseService {
7805
+ /**
7806
+ * Creates a new SyncedCalendarsService instance
7807
+ * @param db - Firestore instance
7808
+ * @param auth - Firebase Auth instance
7809
+ * @param app - Firebase App instance
7810
+ */
7811
+ constructor(db, auth, app) {
7812
+ super(db, auth, app);
7813
+ }
7814
+ // ===== Practitioner Synced Calendars =====
7815
+ /**
7816
+ * Creates a synced calendar for a practitioner
7817
+ * @param practitionerId - ID of the practitioner
7818
+ * @param calendarData - Synced calendar data
7819
+ * @returns Created synced calendar
7820
+ */
7821
+ async createPractitionerSyncedCalendar(practitionerId, calendarData) {
7822
+ return createPractitionerSyncedCalendarUtil(
7823
+ this.db,
7824
+ practitionerId,
7825
+ calendarData,
7826
+ this.generateId.bind(this)
7827
+ );
7828
+ }
7829
+ /**
7830
+ * Gets a synced calendar for a practitioner
7831
+ * @param practitionerId - ID of the practitioner
7832
+ * @param calendarId - ID of the synced calendar
7833
+ * @returns Synced calendar or null if not found
7834
+ */
7835
+ async getPractitionerSyncedCalendar(practitionerId, calendarId) {
7836
+ return getPractitionerSyncedCalendarUtil(
7837
+ this.db,
7838
+ practitionerId,
7839
+ calendarId
7840
+ );
7841
+ }
7842
+ /**
7843
+ * Gets all synced calendars for a practitioner
7844
+ * @param practitionerId - ID of the practitioner
7845
+ * @returns Array of synced calendars
7846
+ */
7847
+ async getPractitionerSyncedCalendars(practitionerId) {
7848
+ return getPractitionerSyncedCalendarsUtil(this.db, practitionerId);
7849
+ }
7850
+ /**
7851
+ * Updates a synced calendar for a practitioner
7852
+ * @param practitionerId - ID of the practitioner
7853
+ * @param calendarId - ID of the synced calendar
7854
+ * @param updateData - Data to update
7855
+ * @returns Updated synced calendar
7856
+ */
7857
+ async updatePractitionerSyncedCalendar(practitionerId, calendarId, updateData) {
7858
+ return updatePractitionerSyncedCalendarUtil(
7859
+ this.db,
7860
+ practitionerId,
7861
+ calendarId,
7862
+ updateData
7863
+ );
7864
+ }
7865
+ /**
7866
+ * Deletes a synced calendar for a practitioner
7867
+ * @param practitionerId - ID of the practitioner
7868
+ * @param calendarId - ID of the synced calendar
7869
+ */
7870
+ async deletePractitionerSyncedCalendar(practitionerId, calendarId) {
7871
+ return deletePractitionerSyncedCalendarUtil(
7872
+ this.db,
7873
+ practitionerId,
7874
+ calendarId
7875
+ );
7876
+ }
7877
+ // ===== Patient Synced Calendars =====
7878
+ /**
7879
+ * Creates a synced calendar for a patient
7880
+ * @param patientId - ID of the patient
7881
+ * @param calendarData - Synced calendar data
7882
+ * @returns Created synced calendar
7883
+ */
7884
+ async createPatientSyncedCalendar(patientId, calendarData) {
7885
+ return createPatientSyncedCalendarUtil(
7886
+ this.db,
7887
+ patientId,
7888
+ calendarData,
7889
+ this.generateId.bind(this)
7890
+ );
7891
+ }
7892
+ /**
7893
+ * Gets a synced calendar for a patient
7894
+ * @param patientId - ID of the patient
7895
+ * @param calendarId - ID of the synced calendar
7896
+ * @returns Synced calendar or null if not found
7897
+ */
7898
+ async getPatientSyncedCalendar(patientId, calendarId) {
7899
+ return getPatientSyncedCalendarUtil(this.db, patientId, calendarId);
7900
+ }
7901
+ /**
7902
+ * Gets all synced calendars for a patient
7903
+ * @param patientId - ID of the patient
7904
+ * @returns Array of synced calendars
7905
+ */
7906
+ async getPatientSyncedCalendars(patientId) {
7907
+ return getPatientSyncedCalendarsUtil(this.db, patientId);
7908
+ }
7909
+ /**
7910
+ * Updates a synced calendar for a patient
7911
+ * @param patientId - ID of the patient
7912
+ * @param calendarId - ID of the synced calendar
7913
+ * @param updateData - Data to update
7914
+ * @returns Updated synced calendar
7915
+ */
7916
+ async updatePatientSyncedCalendar(patientId, calendarId, updateData) {
7917
+ return updatePatientSyncedCalendarUtil(
7918
+ this.db,
7919
+ patientId,
7920
+ calendarId,
7921
+ updateData
7922
+ );
7923
+ }
7924
+ /**
7925
+ * Deletes a synced calendar for a patient
7926
+ * @param patientId - ID of the patient
7927
+ * @param calendarId - ID of the synced calendar
7928
+ */
7929
+ async deletePatientSyncedCalendar(patientId, calendarId) {
7930
+ return deletePatientSyncedCalendarUtil(this.db, patientId, calendarId);
7931
+ }
7932
+ // ===== Clinic Synced Calendars =====
7933
+ /**
7934
+ * Creates a synced calendar for a clinic
7935
+ * @param clinicId - ID of the clinic
7936
+ * @param calendarData - Synced calendar data
7937
+ * @returns Created synced calendar
7938
+ */
7939
+ async createClinicSyncedCalendar(clinicId, calendarData) {
7940
+ return createClinicSyncedCalendarUtil(
7941
+ this.db,
7942
+ clinicId,
7943
+ calendarData,
7944
+ this.generateId.bind(this)
7945
+ );
7946
+ }
7947
+ /**
7948
+ * Gets a synced calendar for a clinic
7949
+ * @param clinicId - ID of the clinic
7950
+ * @param calendarId - ID of the synced calendar
7951
+ * @returns Synced calendar or null if not found
7952
+ */
7953
+ async getClinicSyncedCalendar(clinicId, calendarId) {
7954
+ return getClinicSyncedCalendarUtil(this.db, clinicId, calendarId);
7955
+ }
7956
+ /**
7957
+ * Gets all synced calendars for a clinic
7958
+ * @param clinicId - ID of the clinic
7959
+ * @returns Array of synced calendars
7960
+ */
7961
+ async getClinicSyncedCalendars(clinicId) {
7962
+ return getClinicSyncedCalendarsUtil(this.db, clinicId);
7963
+ }
7964
+ /**
7965
+ * Updates a synced calendar for a clinic
7966
+ * @param clinicId - ID of the clinic
7967
+ * @param calendarId - ID of the synced calendar
7968
+ * @param updateData - Data to update
7969
+ * @returns Updated synced calendar
7970
+ */
7971
+ async updateClinicSyncedCalendar(clinicId, calendarId, updateData) {
7972
+ return updateClinicSyncedCalendarUtil(
7973
+ this.db,
7974
+ clinicId,
7975
+ calendarId,
7976
+ updateData
7977
+ );
7978
+ }
7979
+ /**
7980
+ * Deletes a synced calendar for a clinic
7981
+ * @param clinicId - ID of the clinic
7982
+ * @param calendarId - ID of the synced calendar
7983
+ */
7984
+ async deleteClinicSyncedCalendar(clinicId, calendarId) {
7985
+ return deleteClinicSyncedCalendarUtil(this.db, clinicId, calendarId);
7986
+ }
7987
+ // ===== Google Calendar Integration =====
7988
+ /**
7989
+ * Gets the OAuth URL for Google Calendar
7990
+ * @param scopes - OAuth scopes to request
7991
+ * @returns OAuth URL
7992
+ */
7993
+ getGoogleCalendarOAuthUrl(scopes = ["https://www.googleapis.com/auth/calendar"]) {
7994
+ return getGoogleCalendarOAuthUrlUtil(scopes);
7995
+ }
7996
+ /**
7997
+ * Authenticates with Google Calendar using an authorization code
7998
+ * @param authCode - Authorization code from Google OAuth
7999
+ * @returns Access token, refresh token, and expiration time
8000
+ */
8001
+ async authenticateWithGoogleCalendar(authCode) {
8002
+ return authenticateWithGoogleCalendarUtil(authCode);
8003
+ }
8004
+ /**
8005
+ * Lists available Google Calendars for a user
8006
+ * @param accessToken - Google API access token
8007
+ * @returns List of available calendars
8008
+ */
8009
+ async listGoogleCalendars(accessToken) {
8010
+ return listGoogleCalendarsUtil(accessToken);
8011
+ }
8012
+ /**
8013
+ * Syncs events from our system to Google Calendar for a practitioner
8014
+ * @param practitionerId - ID of the practitioner
8015
+ * @param calendarId - ID of the synced calendar
8016
+ * @param events - Events to sync
8017
+ * @param existingSyncId - Optional existing sync ID for updating an event
8018
+ * @returns Result of the sync operation
8019
+ */
8020
+ async syncPractitionerEventsToGoogleCalendar(practitionerId, calendarId, events, existingSyncId) {
8021
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
8022
+ practitionerId,
8023
+ calendarId
8024
+ );
8025
+ if (!syncedCalendar) {
8026
+ throw new Error("Synced calendar not found");
8027
+ }
8028
+ return syncEventsToGoogleCalendarUtil(
8029
+ this.db,
8030
+ "practitioner",
8031
+ practitionerId,
8032
+ syncedCalendar,
8033
+ events,
8034
+ existingSyncId
8035
+ );
8036
+ }
8037
+ /**
8038
+ * Syncs events from our system to Google Calendar for a patient
8039
+ * @param patientId - ID of the patient
8040
+ * @param calendarId - ID of the synced calendar
8041
+ * @param events - Events to sync
8042
+ * @param existingSyncId - Optional existing sync ID for updating an event
8043
+ * @returns Result of the sync operation
8044
+ */
8045
+ async syncPatientEventsToGoogleCalendar(patientId, calendarId, events, existingSyncId) {
8046
+ const syncedCalendar = await this.getPatientSyncedCalendar(
8047
+ patientId,
8048
+ calendarId
8049
+ );
8050
+ if (!syncedCalendar) {
8051
+ throw new Error("Synced calendar not found");
8052
+ }
8053
+ return syncEventsToGoogleCalendarUtil(
8054
+ this.db,
8055
+ "patient",
8056
+ patientId,
8057
+ syncedCalendar,
8058
+ events,
8059
+ existingSyncId
8060
+ );
8061
+ }
8062
+ /**
8063
+ * Syncs events from our system to Google Calendar for a clinic
8064
+ * @param clinicId - ID of the clinic
8065
+ * @param calendarId - ID of the synced calendar
8066
+ * @param events - Events to sync
8067
+ * @returns Result of the sync operation
8068
+ */
8069
+ async syncClinicEventsToGoogleCalendar(clinicId, calendarId, events) {
8070
+ const syncedCalendar = await this.getClinicSyncedCalendar(
8071
+ clinicId,
8072
+ calendarId
8073
+ );
8074
+ if (!syncedCalendar) {
8075
+ throw new Error("Synced calendar not found");
8076
+ }
8077
+ return syncEventsToGoogleCalendarUtil(
8078
+ this.db,
8079
+ "clinic",
8080
+ clinicId,
8081
+ syncedCalendar,
8082
+ events
8083
+ );
8084
+ }
8085
+ /**
8086
+ * Fetches events from Google Calendar for a practitioner
8087
+ * @param practitionerId - ID of the practitioner
8088
+ * @param calendarId - ID of the synced calendar
8089
+ * @param startDate - Start date for fetching events
8090
+ * @param endDate - End date for fetching events
8091
+ * @returns Events fetched from Google Calendar
8092
+ */
8093
+ async fetchEventsFromPractitionerGoogleCalendar(practitionerId, calendarId, startDate, endDate) {
8094
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
8095
+ practitionerId,
8096
+ calendarId
8097
+ );
8098
+ if (!syncedCalendar) {
8099
+ throw new Error("Synced calendar not found");
8100
+ }
8101
+ return fetchEventsFromGoogleCalendarUtil(
8102
+ this.db,
8103
+ "practitioner",
8104
+ practitionerId,
8105
+ syncedCalendar,
8106
+ startDate,
8107
+ endDate
8108
+ );
8109
+ }
8110
+ /**
8111
+ * Fetches events from Google Calendar for a patient
8112
+ * @param patientId - ID of the patient
8113
+ * @param calendarId - ID of the synced calendar
8114
+ * @param startDate - Start date for fetching events
8115
+ * @param endDate - End date for fetching events
8116
+ * @returns Events fetched from Google Calendar
8117
+ */
8118
+ async fetchEventsFromPatientGoogleCalendar(patientId, calendarId, startDate, endDate) {
8119
+ const syncedCalendar = await this.getPatientSyncedCalendar(
8120
+ patientId,
8121
+ calendarId
8122
+ );
8123
+ if (!syncedCalendar) {
8124
+ throw new Error("Synced calendar not found");
8125
+ }
8126
+ return fetchEventsFromGoogleCalendarUtil(
8127
+ this.db,
8128
+ "patient",
8129
+ patientId,
8130
+ syncedCalendar,
8131
+ startDate,
8132
+ endDate
8133
+ );
8134
+ }
8135
+ /**
8136
+ * Fetches events from Google Calendar for a clinic
8137
+ * @param clinicId - ID of the clinic
8138
+ * @param calendarId - ID of the synced calendar
8139
+ * @param startDate - Start date for fetching events
8140
+ * @param endDate - End date for fetching events
8141
+ * @returns Events fetched from Google Calendar
8142
+ */
8143
+ async fetchEventsFromClinicGoogleCalendar(clinicId, calendarId, startDate, endDate) {
8144
+ const syncedCalendar = await this.getClinicSyncedCalendar(
8145
+ clinicId,
8146
+ calendarId
8147
+ );
8148
+ if (!syncedCalendar) {
8149
+ throw new Error("Synced calendar not found");
8150
+ }
8151
+ return fetchEventsFromGoogleCalendarUtil(
8152
+ this.db,
8153
+ "clinic",
8154
+ clinicId,
8155
+ syncedCalendar,
8156
+ startDate,
8157
+ endDate
8158
+ );
8159
+ }
8160
+ /**
8161
+ * Deletes an event from Google Calendar for a practitioner
8162
+ * @param practitionerId - ID of the practitioner
8163
+ * @param calendarId - ID of the synced calendar
8164
+ * @param eventId - ID of the event in Google Calendar
8165
+ * @returns Success status
8166
+ */
8167
+ async deletePractitionerGoogleCalendarEvent(practitionerId, calendarId, eventId) {
8168
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
8169
+ practitionerId,
8170
+ calendarId
8171
+ );
8172
+ if (!syncedCalendar) {
8173
+ throw new Error("Synced calendar not found");
8174
+ }
8175
+ return deleteGoogleCalendarEventUtil(
8176
+ this.db,
8177
+ "practitioner",
8178
+ practitionerId,
8179
+ syncedCalendar,
8180
+ eventId
8181
+ );
8182
+ }
8183
+ /**
8184
+ * Deletes an event from Google Calendar for a patient
8185
+ * @param patientId - ID of the patient
8186
+ * @param calendarId - ID of the synced calendar
8187
+ * @param eventId - ID of the event in Google Calendar
8188
+ * @returns Success status
8189
+ */
8190
+ async deletePatientGoogleCalendarEvent(patientId, calendarId, eventId) {
8191
+ const syncedCalendar = await this.getPatientSyncedCalendar(
8192
+ patientId,
8193
+ calendarId
8194
+ );
8195
+ if (!syncedCalendar) {
8196
+ throw new Error("Synced calendar not found");
8197
+ }
8198
+ return deleteGoogleCalendarEventUtil(
8199
+ this.db,
8200
+ "patient",
8201
+ patientId,
8202
+ syncedCalendar,
8203
+ eventId
8204
+ );
8205
+ }
8206
+ /**
8207
+ * Deletes an event from Google Calendar for a clinic
8208
+ * @param clinicId - ID of the clinic
8209
+ * @param calendarId - ID of the synced calendar
8210
+ * @param eventId - ID of the event in Google Calendar
8211
+ * @returns Success status
8212
+ */
8213
+ async deleteClinicGoogleCalendarEvent(clinicId, calendarId, eventId) {
8214
+ const syncedCalendar = await this.getClinicSyncedCalendar(
8215
+ clinicId,
8216
+ calendarId
8217
+ );
8218
+ if (!syncedCalendar) {
8219
+ throw new Error("Synced calendar not found");
8220
+ }
8221
+ return deleteGoogleCalendarEventUtil(
8222
+ this.db,
8223
+ "clinic",
8224
+ clinicId,
8225
+ syncedCalendar,
8226
+ eventId
8227
+ );
8228
+ }
8229
+ /**
8230
+ * Converts Google Calendar events to our system's format for a practitioner
8231
+ * @param practitionerId - ID of the practitioner
8232
+ * @param googleEvents - Google Calendar events
8233
+ * @returns Converted calendar events
8234
+ */
8235
+ convertGoogleEventsToPractitionerEvents(practitionerId, googleEvents) {
8236
+ return googleEvents.map(
8237
+ (event) => convertGoogleEventToCalendarEventUtil(
8238
+ event,
8239
+ practitionerId,
8240
+ "practitioner"
8241
+ )
8242
+ );
8243
+ }
8244
+ /**
8245
+ * Converts Google Calendar events to our system's format for a patient
8246
+ * @param patientId - ID of the patient
8247
+ * @param googleEvents - Google Calendar events
8248
+ * @returns Converted calendar events
8249
+ */
8250
+ convertGoogleEventsToPatientEvents(patientId, googleEvents) {
8251
+ return googleEvents.map(
8252
+ (event) => convertGoogleEventToCalendarEventUtil(event, patientId, "patient")
8253
+ );
8254
+ }
8255
+ /**
8256
+ * Converts Google Calendar events to our system's format for a clinic
8257
+ * @param clinicId - ID of the clinic
8258
+ * @param googleEvents - Google Calendar events
8259
+ * @returns Converted calendar events
8260
+ */
8261
+ convertGoogleEventsToClinicEvents(clinicId, googleEvents) {
8262
+ return googleEvents.map(
8263
+ (event) => convertGoogleEventToCalendarEventUtil(event, clinicId, "clinic")
8264
+ );
8265
+ }
8266
+ /**
8267
+ * Fetches a single event from Google Calendar for a practitioner
8268
+ * @param practitionerId - ID of the practitioner
8269
+ * @param calendarId - ID of the synced calendar
8270
+ * @param eventId - ID of the event in Google Calendar
8271
+ * @returns The event data or null if not found
8272
+ */
8273
+ async fetchEventFromPractitionerGoogleCalendar(practitionerId, calendarId, eventId) {
8274
+ var _a;
8275
+ const syncedCalendar = await this.getPractitionerSyncedCalendar(
8276
+ practitionerId,
8277
+ calendarId
8278
+ );
8279
+ if (!syncedCalendar) {
8280
+ throw new Error("Synced calendar not found");
8281
+ }
8282
+ try {
8283
+ const { accessToken } = await refreshGoogleCalendarTokenUtil(
8284
+ syncedCalendar.refreshToken
8285
+ );
8286
+ const response = await makeRequest(
8287
+ "get",
8288
+ `${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${eventId}`,
8289
+ { Authorization: `Bearer ${accessToken}` }
8290
+ );
8291
+ await updateLastSyncedTimestampUtil(
8292
+ this.db,
8293
+ "practitioner",
8294
+ practitionerId,
8295
+ syncedCalendar.id
8296
+ );
8297
+ return response;
8298
+ } catch (error) {
8299
+ if (((_a = error.response) == null ? void 0 : _a.status) === 404) {
8300
+ return null;
8301
+ }
8302
+ console.error(
8303
+ `Error fetching event from Google Calendar: ${error.message}`
8304
+ );
8305
+ throw error;
8306
+ }
8307
+ }
8308
+ };
8309
+
8310
+ // src/services/calendar/calendar-refactored.service.ts
8311
+ var MIN_APPOINTMENT_DURATION2 = 15;
8312
+ var CalendarServiceV2 = class extends BaseService {
8313
+ /**
8314
+ * Creates a new CalendarService instance
8315
+ * @param db - Firestore instance
8316
+ * @param auth - Firebase Auth instance
8317
+ * @param app - Firebase App instance
8318
+ */
8319
+ constructor(db, auth, app) {
8320
+ super(db, auth, app);
8321
+ this.syncedCalendarsService = new SyncedCalendarsService(db, auth, app);
8322
+ }
8323
+ // #region Public API Methods
8324
+ /**
8325
+ * Creates a new appointment with proper validation and scheduling rules
8326
+ * @param params - Appointment creation parameters
8327
+ * @returns Created calendar event
8328
+ */
8329
+ async createAppointment(params) {
8330
+ await this.validateAppointmentParams(params);
8331
+ await this.validateClinicWorkingHours(params.clinicId, params.eventTime);
8332
+ await this.validateDoctorAvailability(
8333
+ params.doctorId,
8334
+ params.eventTime,
8335
+ params.clinicId
8336
+ );
8337
+ const { clinicInfo, practitionerInfo, patientInfo } = await this.fetchProfileInfoCards(
8338
+ params.clinicId,
8339
+ params.doctorId,
8340
+ params.patientId
8341
+ );
8342
+ const appointmentData = {
8343
+ clinicBranchId: params.clinicId,
8344
+ clinicBranchInfo: clinicInfo,
8345
+ practitionerProfileId: params.doctorId,
8346
+ practitionerProfileInfo: practitionerInfo,
8347
+ patientProfileId: params.patientId,
8348
+ patientProfileInfo: patientInfo,
8349
+ procedureId: params.procedureId,
8350
+ eventLocation: params.eventLocation,
8351
+ eventName: "Appointment",
8352
+ // TODO: Add procedure name when procedure model is available
8353
+ eventTime: params.eventTime,
8354
+ description: params.description || "",
8355
+ status: "pending" /* PENDING */,
8356
+ syncStatus: "internal" /* INTERNAL */,
8357
+ eventType: "appointment" /* APPOINTMENT */
8358
+ };
8359
+ const appointment = await createAppointmentUtil(
8360
+ this.db,
8361
+ params.clinicId,
8362
+ params.doctorId,
8363
+ params.patientId,
8364
+ appointmentData,
8365
+ this.generateId.bind(this)
8366
+ );
8367
+ await this.syncAppointmentWithExternalCalendars(appointment);
8368
+ return appointment;
8369
+ }
8370
+ /**
8371
+ * Updates an existing appointment
8372
+ * @param params - Appointment update parameters
8373
+ * @returns Updated calendar event
8374
+ */
8375
+ async updateAppointment(params) {
8376
+ await this.validateUpdatePermissions(params);
8377
+ const updateData = {
8378
+ eventTime: params.eventTime,
8379
+ description: params.description,
8380
+ status: params.status
8381
+ };
8382
+ const appointment = await updateAppointmentUtil(
8383
+ this.db,
8384
+ params.clinicId,
8385
+ params.doctorId,
8386
+ params.patientId,
8387
+ params.appointmentId,
8388
+ updateData
8389
+ );
8390
+ await this.syncAppointmentWithExternalCalendars(appointment);
8391
+ return appointment;
8392
+ }
8393
+ /**
8394
+ * Gets available appointment slots for a doctor at a clinic
8395
+ * @param clinicId - ID of the clinic
8396
+ * @param doctorId - ID of the doctor
8397
+ * @param date - Date to check availability for
8398
+ * @returns Array of available time slots
8399
+ */
8400
+ async getAvailableSlots(clinicId, doctorId, date) {
8401
+ const workingHours = await this.getClinicWorkingHours(clinicId, date);
8402
+ const doctorSchedule = await this.getDoctorSchedule(doctorId, date);
8403
+ const existingAppointments = await this.getDoctorAppointments(
8404
+ doctorId,
8405
+ date
8406
+ );
8407
+ return this.calculateAvailableSlots(
8408
+ workingHours,
8409
+ doctorSchedule,
8410
+ existingAppointments
8411
+ );
8412
+ }
8413
+ /**
8414
+ * Confirms an appointment
8415
+ * @param appointmentId - ID of the appointment
8416
+ * @param clinicId - ID of the clinic
8417
+ * @returns Confirmed calendar event
8418
+ */
8419
+ async confirmAppointment(appointmentId, clinicId) {
8420
+ return this.updateAppointmentStatus(
8421
+ appointmentId,
8422
+ clinicId,
8423
+ "confirmed" /* CONFIRMED */
8424
+ );
8425
+ }
8426
+ /**
8427
+ * Rejects an appointment
8428
+ * @param appointmentId - ID of the appointment
8429
+ * @param clinicId - ID of the clinic
8430
+ * @returns Rejected calendar event
8431
+ */
8432
+ async rejectAppointment(appointmentId, clinicId) {
8433
+ return this.updateAppointmentStatus(
8434
+ appointmentId,
8435
+ clinicId,
8436
+ "rejected" /* REJECTED */
8437
+ );
8438
+ }
8439
+ /**
8440
+ * Cancels an appointment
8441
+ * @param appointmentId - ID of the appointment
8442
+ * @param clinicId - ID of the clinic
8443
+ * @returns Canceled calendar event
8444
+ */
8445
+ async cancelAppointment(appointmentId, clinicId) {
8446
+ return this.updateAppointmentStatus(
8447
+ appointmentId,
8448
+ clinicId,
8449
+ "canceled" /* CANCELED */
8450
+ );
8451
+ }
8452
+ /**
8453
+ * Imports events from external calendars
8454
+ * @param entityType - Type of entity (practitioner or patient)
8455
+ * @param entityId - ID of the entity
8456
+ * @param startDate - Start date for fetching events
8457
+ * @param endDate - End date for fetching events
8458
+ * @returns Number of events imported
8459
+ */
8460
+ async importEventsFromExternalCalendars(entityType, entityId, startDate, endDate) {
8461
+ if (entityType === "patient") {
8462
+ return 0;
8463
+ }
8464
+ const syncedCalendars = await this.syncedCalendarsService.getPractitionerSyncedCalendars(
8465
+ entityId
8466
+ );
8467
+ const activeCalendars = syncedCalendars.filter((cal) => cal.isActive);
8468
+ if (activeCalendars.length === 0) {
8469
+ return 0;
8470
+ }
8471
+ let importedEventsCount = 0;
8472
+ const currentTime = Timestamp23.now();
8473
+ for (const calendar of activeCalendars) {
8474
+ try {
8475
+ let externalEvents = [];
8476
+ if (calendar.provider === "google" /* GOOGLE */) {
8477
+ externalEvents = await this.syncedCalendarsService.fetchEventsFromPractitionerGoogleCalendar(
8478
+ entityId,
8479
+ calendar.id,
8480
+ startDate,
8481
+ endDate
8482
+ );
8483
+ }
8484
+ for (const externalEvent of externalEvents) {
8485
+ try {
8486
+ const convertedEvent = this.syncedCalendarsService.convertGoogleEventsToPractitionerEvents(
8487
+ entityId,
8488
+ [externalEvent]
8489
+ )[0];
8490
+ if (!convertedEvent.eventTime) {
8491
+ continue;
8492
+ }
8493
+ const eventData = {
8494
+ // Ensure all required fields are set
8495
+ eventName: convertedEvent.eventName || "External Event",
8496
+ eventTime: convertedEvent.eventTime,
8497
+ description: convertedEvent.description || "",
8498
+ status: "confirmed" /* CONFIRMED */,
8499
+ syncStatus: "external" /* EXTERNAL */,
8500
+ eventType: "blocking" /* BLOCKING */,
8501
+ practitionerProfileId: entityId,
8502
+ syncedCalendarEventId: [
8503
+ {
8504
+ eventId: externalEvent.id,
8505
+ syncedCalendarProvider: calendar.provider,
8506
+ syncedAt: currentTime
8507
+ }
8508
+ ]
8509
+ };
8510
+ const doctorEvent = await this.createDoctorBlockingEvent(
8511
+ entityId,
8512
+ eventData
8513
+ );
8514
+ if (doctorEvent) {
8515
+ importedEventsCount++;
8516
+ }
8517
+ } catch (eventError) {
8518
+ console.error("Error importing event:", eventError);
8519
+ }
8520
+ }
8521
+ } catch (calendarError) {
8522
+ console.error(
8523
+ `Error fetching events from calendar ${calendar.id}:`,
8524
+ calendarError
8525
+ );
8526
+ }
8527
+ }
8528
+ return importedEventsCount;
8529
+ }
8530
+ /**
8531
+ * Creates a blocking event in a doctor's calendar
8532
+ * @param doctorId - ID of the doctor
8533
+ * @param eventData - Calendar event data
8534
+ * @returns Created calendar event
8535
+ */
8536
+ async createDoctorBlockingEvent(doctorId, eventData) {
8537
+ try {
8538
+ const eventId = this.generateId();
8539
+ const eventRef = doc19(
8540
+ this.db,
8541
+ PRACTITIONERS_COLLECTION,
8542
+ doctorId,
8543
+ CALENDAR_COLLECTION,
8544
+ eventId
8545
+ );
8546
+ const newEvent = {
8547
+ id: eventId,
8548
+ ...eventData,
8549
+ createdAt: serverTimestamp18(),
8550
+ updatedAt: serverTimestamp18()
8551
+ };
8552
+ await setDoc19(eventRef, newEvent);
8553
+ return {
8554
+ ...newEvent,
8555
+ createdAt: Timestamp23.now(),
8556
+ updatedAt: Timestamp23.now()
8557
+ };
8558
+ } catch (error) {
8559
+ console.error(
8560
+ `Error creating blocking event for doctor ${doctorId}:`,
8561
+ error
8562
+ );
8563
+ return null;
8564
+ }
8565
+ }
8566
+ /**
8567
+ * Periodically syncs events from external calendars for doctors
8568
+ * This would be called via a scheduled Cloud Function
8569
+ * @param lookbackDays - Number of days to look back for events
8570
+ * @param lookforwardDays - Number of days to look forward for events
8571
+ */
8572
+ async synchronizeExternalCalendars(lookbackDays = 7, lookforwardDays = 30) {
8573
+ try {
8574
+ const practitionersRef = collection17(this.db, PRACTITIONERS_COLLECTION);
8575
+ const practitionersSnapshot = await getDocs16(practitionersRef);
8576
+ const startDate = /* @__PURE__ */ new Date();
8577
+ startDate.setDate(startDate.getDate() - lookbackDays);
8578
+ const endDate = /* @__PURE__ */ new Date();
8579
+ endDate.setDate(endDate.getDate() + lookforwardDays);
8580
+ const syncPromises = [];
8581
+ for (const docSnapshot of practitionersSnapshot.docs) {
8582
+ const practitionerId = docSnapshot.id;
8583
+ syncPromises.push(
8584
+ this.importEventsFromExternalCalendars(
8585
+ "doctor",
8586
+ practitionerId,
8587
+ startDate,
8588
+ endDate
8589
+ ).then((count) => {
8590
+ console.log(
8591
+ `Imported ${count} events for doctor ${practitionerId}`
8592
+ );
8593
+ }).catch((error) => {
8594
+ console.error(
8595
+ `Error importing events for doctor ${practitionerId}:`,
8596
+ error
8597
+ );
8598
+ })
8599
+ );
8600
+ syncPromises.push(
8601
+ this.updateExistingEventsFromExternalCalendars(
8602
+ practitionerId,
8603
+ startDate,
8604
+ endDate
8605
+ ).then((count) => {
8606
+ console.log(
8607
+ `Updated ${count} events for doctor ${practitionerId}`
8608
+ );
8609
+ }).catch((error) => {
8610
+ console.error(
8611
+ `Error updating events for doctor ${practitionerId}:`,
8612
+ error
8613
+ );
8614
+ })
8615
+ );
8616
+ }
8617
+ await Promise.all(syncPromises);
8618
+ console.log("Completed external calendar synchronization");
8619
+ } catch (error) {
8620
+ console.error("Error synchronizing external calendars:", error);
8621
+ }
8622
+ }
8623
+ /**
8624
+ * Updates existing events that were synced from external calendars
8625
+ * @param doctorId - ID of the doctor
8626
+ * @param startDate - Start date for fetching events
8627
+ * @param endDate - End date for fetching events
8628
+ * @returns Number of events updated
8629
+ */
8630
+ async updateExistingEventsFromExternalCalendars(doctorId, startDate, endDate) {
8631
+ var _a;
8632
+ try {
8633
+ const eventsRef = collection17(
8634
+ this.db,
8635
+ PRACTITIONERS_COLLECTION,
8636
+ doctorId,
8637
+ CALENDAR_COLLECTION
8638
+ );
8639
+ const q = query16(
8640
+ eventsRef,
8641
+ where16("syncStatus", "==", "external" /* EXTERNAL */),
8642
+ where16("eventTime.start", ">=", Timestamp23.fromDate(startDate)),
8643
+ where16("eventTime.start", "<=", Timestamp23.fromDate(endDate))
8644
+ );
8645
+ const eventsSnapshot = await getDocs16(q);
8646
+ const events = eventsSnapshot.docs.map((doc20) => ({
8647
+ id: doc20.id,
8648
+ ...doc20.data()
8649
+ }));
8650
+ const calendars = await this.syncedCalendarsService.getPractitionerSyncedCalendars(
8651
+ doctorId
8652
+ );
8653
+ const activeCalendars = calendars.filter((cal) => cal.isActive);
8654
+ if (activeCalendars.length === 0 || events.length === 0) {
8655
+ return 0;
8656
+ }
8657
+ let updatedCount = 0;
8658
+ for (const event of events) {
8659
+ if (!((_a = event.syncedCalendarEventId) == null ? void 0 : _a.length)) continue;
8660
+ for (const syncId of event.syncedCalendarEventId) {
8661
+ const calendar = activeCalendars.find(
8662
+ (cal) => cal.provider === syncId.syncedCalendarProvider
8663
+ );
8664
+ if (!calendar) continue;
8665
+ if (syncId.syncedCalendarProvider === "google" /* GOOGLE */) {
8666
+ try {
8667
+ const externalEvent = await this.fetchExternalEvent(
8668
+ doctorId,
8669
+ calendar,
8670
+ syncId.eventId
8671
+ );
8672
+ if (externalEvent) {
8673
+ const externalStartTime = new Date(
8674
+ externalEvent.start.dateTime || externalEvent.start.date
8675
+ ).getTime();
8676
+ const externalEndTime = new Date(
8677
+ externalEvent.end.dateTime || externalEvent.end.date
8678
+ ).getTime();
8679
+ const localStartTime = event.eventTime.start.toDate().getTime();
8680
+ const localEndTime = event.eventTime.end.toDate().getTime();
8681
+ if (externalStartTime !== localStartTime || externalEndTime !== localEndTime || externalEvent.summary !== event.eventName || externalEvent.description !== event.description) {
8682
+ await this.updateLocalEventFromExternal(
8683
+ doctorId,
8684
+ event.id,
8685
+ externalEvent
8686
+ );
8687
+ updatedCount++;
8688
+ }
8689
+ } else {
8690
+ await this.updateEventStatus(
8691
+ doctorId,
8692
+ event.id,
8693
+ "canceled" /* CANCELED */
8694
+ );
8695
+ updatedCount++;
8696
+ }
8697
+ } catch (error) {
8698
+ console.error(
8699
+ `Error updating external event ${event.id}:`,
8700
+ error
8701
+ );
8702
+ }
8703
+ }
8704
+ }
8705
+ }
8706
+ return updatedCount;
8707
+ } catch (error) {
8708
+ console.error(
8709
+ "Error updating existing events from external calendars:",
8710
+ error
8711
+ );
8712
+ return 0;
8713
+ }
8714
+ }
8715
+ /**
8716
+ * Fetches a single external event from Google Calendar
8717
+ * @param doctorId - ID of the doctor
8718
+ * @param calendar - Calendar information
8719
+ * @param externalEventId - ID of the external event
8720
+ * @returns External event data or null if not found
8721
+ */
8722
+ async fetchExternalEvent(doctorId, calendar, externalEventId) {
8723
+ try {
8724
+ if (calendar.provider === "google" /* GOOGLE */) {
8725
+ const result = await this.syncedCalendarsService.fetchEventFromPractitionerGoogleCalendar(
8726
+ doctorId,
8727
+ calendar.id,
8728
+ externalEventId
8729
+ );
8730
+ return result;
8731
+ }
8732
+ return null;
8733
+ } catch (error) {
8734
+ console.error(`Error fetching external event ${externalEventId}:`, error);
8735
+ return null;
8736
+ }
8737
+ }
8738
+ /**
8739
+ * Updates a local event with data from an external event
8740
+ * @param doctorId - ID of the doctor
8741
+ * @param eventId - ID of the local event
8742
+ * @param externalEvent - External event data
8743
+ */
8744
+ async updateLocalEventFromExternal(doctorId, eventId, externalEvent) {
8745
+ try {
8746
+ const startTime = new Date(
8747
+ externalEvent.start.dateTime || externalEvent.start.date
8748
+ );
8749
+ const endTime = new Date(
8750
+ externalEvent.end.dateTime || externalEvent.end.date
8751
+ );
8752
+ const eventRef = doc19(
8753
+ this.db,
8754
+ PRACTITIONERS_COLLECTION,
8755
+ doctorId,
8756
+ CALENDAR_COLLECTION,
8757
+ eventId
8758
+ );
8759
+ await updateDoc20(eventRef, {
8760
+ eventName: externalEvent.summary || "External Event",
8761
+ eventTime: {
8762
+ start: Timestamp23.fromDate(startTime),
8763
+ end: Timestamp23.fromDate(endTime)
8764
+ },
8765
+ description: externalEvent.description || "",
8766
+ updatedAt: serverTimestamp18()
8767
+ });
8768
+ console.log(`Updated local event ${eventId} from external event`);
8769
+ } catch (error) {
8770
+ console.error(
8771
+ `Error updating local event ${eventId} from external:`,
8772
+ error
8773
+ );
8774
+ }
8775
+ }
8776
+ /**
8777
+ * Updates an event's status
8778
+ * @param doctorId - ID of the doctor
8779
+ * @param eventId - ID of the event
8780
+ * @param status - New status
8781
+ */
8782
+ async updateEventStatus(doctorId, eventId, status) {
8783
+ try {
8784
+ const eventRef = doc19(
8785
+ this.db,
8786
+ PRACTITIONERS_COLLECTION,
8787
+ doctorId,
8788
+ CALENDAR_COLLECTION,
8789
+ eventId
8790
+ );
8791
+ await updateDoc20(eventRef, {
8792
+ status,
8793
+ updatedAt: serverTimestamp18()
8794
+ });
8795
+ console.log(`Updated event ${eventId} status to ${status}`);
8796
+ } catch (error) {
8797
+ console.error(`Error updating event ${eventId} status:`, error);
8798
+ }
8799
+ }
8800
+ /**
8801
+ * Creates a scheduled job to periodically sync external calendars
8802
+ * Note: This would be implemented using Cloud Functions in a real application
8803
+ * This is a sample implementation to show how it could be set up
8804
+ * @param interval - Interval in hours
8805
+ */
8806
+ createScheduledSyncJob(interval = 3) {
8807
+ console.log(
8808
+ `Setting up scheduled calendar sync job every ${interval} hours`
8809
+ );
8810
+ }
8811
+ // #endregion
8812
+ // #region Private Helper Methods
8813
+ /**
8814
+ * Validates appointment creation parameters
8815
+ * @param params - Appointment parameters to validate
8816
+ * @throws Error if validation fails
8817
+ */
8818
+ async validateAppointmentParams(params) {
8819
+ await createAppointmentSchema.parseAsync(params);
8820
+ }
8821
+ /**
8822
+ * Validates if the event time falls within clinic working hours
8823
+ * @param clinicId - ID of the clinic
8824
+ * @param eventTime - Event time to validate
8825
+ * @throws Error if validation fails
8826
+ */
8827
+ async validateClinicWorkingHours(clinicId, eventTime) {
8828
+ const startDate = eventTime.start.toDate();
8829
+ const workingHours = await this.getClinicWorkingHours(clinicId, startDate);
8830
+ if (workingHours.length === 0) {
8831
+ throw new Error("Clinic is not open on this day");
8832
+ }
8833
+ const startTime = startDate;
8834
+ const endTime = eventTime.end.toDate();
8835
+ const isWithinWorkingHours = workingHours.some((slot) => {
8836
+ return slot.start <= startTime && slot.end >= endTime && slot.isAvailable;
8837
+ });
8838
+ if (!isWithinWorkingHours) {
8839
+ throw new Error("Appointment time is outside clinic working hours");
8840
+ }
8841
+ }
8842
+ /**
8843
+ * Validates if the doctor is available during the event time
8844
+ * @param doctorId - ID of the doctor
8845
+ * @param eventTime - Event time to validate
8846
+ * @param clinicId - ID of the clinic where the appointment is being booked
8847
+ * @throws Error if validation fails
8848
+ */
8849
+ async validateDoctorAvailability(doctorId, eventTime, clinicId) {
8850
+ var _a;
8851
+ const startDate = eventTime.start.toDate();
8852
+ const startTime = startDate;
8853
+ const endTime = eventTime.end.toDate();
8854
+ const practitionerRef = doc19(this.db, PRACTITIONERS_COLLECTION, doctorId);
8855
+ const practitionerDoc = await getDoc22(practitionerRef);
8856
+ if (!practitionerDoc.exists()) {
8857
+ throw new Error(`Doctor with ID ${doctorId} not found`);
8858
+ }
8859
+ const practitioner = practitionerDoc.data();
8860
+ if (!practitioner.clinics.includes(clinicId)) {
8861
+ throw new Error("Doctor does not work at this clinic");
8862
+ }
8863
+ const clinicWorkingHours = (_a = practitioner.clinicWorkingHours) == null ? void 0 : _a.find(
8864
+ (hours) => hours.clinicId === clinicId && hours.isActive
8865
+ );
8866
+ if (!clinicWorkingHours) {
8867
+ throw new Error("Doctor does not have working hours set for this clinic");
8868
+ }
8869
+ const dayOfWeek = startDate.getDay();
8870
+ const dayKey = [
8871
+ "sunday",
8872
+ "monday",
8873
+ "tuesday",
8874
+ "wednesday",
8875
+ "thursday",
8876
+ "friday",
8877
+ "saturday"
8878
+ ][dayOfWeek];
8879
+ const daySchedule = clinicWorkingHours.workingHours[dayKey];
8880
+ if (!daySchedule) {
8881
+ throw new Error("Doctor is not working on this day at this clinic");
8882
+ }
8883
+ const [startHour, startMinute] = daySchedule.start.split(":").map(Number);
8884
+ const [endHour, endMinute] = daySchedule.end.split(":").map(Number);
8885
+ const scheduleStart = new Date(startDate);
8886
+ scheduleStart.setHours(startHour, startMinute, 0, 0);
8887
+ const scheduleEnd = new Date(startDate);
8888
+ scheduleEnd.setHours(endHour, endMinute, 0, 0);
8889
+ if (startTime < scheduleStart || endTime > scheduleEnd) {
8890
+ throw new Error(
8891
+ "Appointment time is outside doctor's working hours at this clinic"
8892
+ );
8893
+ }
8894
+ const appointments = await this.getDoctorAppointments(doctorId, startDate);
8895
+ const hasOverlap = appointments.some((appointment) => {
8896
+ const appointmentStart = appointment.eventTime.start.toDate();
8897
+ const appointmentEnd = appointment.eventTime.end.toDate();
8898
+ return startTime >= appointmentStart && startTime < appointmentEnd || endTime > appointmentStart && endTime <= appointmentEnd || startTime <= appointmentStart && endTime >= appointmentEnd;
8899
+ });
8900
+ if (hasOverlap) {
8901
+ throw new Error("Doctor has another appointment during this time");
8902
+ }
8903
+ }
8904
+ /**
8905
+ * Updates appointment status
8906
+ * @param appointmentId - ID of the appointment
8907
+ * @param clinicId - ID of the clinic
8908
+ * @param status - New status
8909
+ * @returns Updated calendar event
8910
+ */
8911
+ async updateAppointmentStatus(appointmentId, clinicId, status) {
8912
+ const appointmentRef = doc19(this.db, CALENDAR_COLLECTION, appointmentId);
8913
+ const appointmentDoc = await getDoc22(appointmentRef);
8914
+ if (!appointmentDoc.exists()) {
8915
+ throw new Error(`Appointment with ID ${appointmentId} not found`);
8916
+ }
8917
+ const appointment = appointmentDoc.data();
8918
+ if (appointment.clinicBranchId !== clinicId) {
8919
+ throw new Error("Appointment does not belong to the specified clinic");
8920
+ }
8921
+ this.validateStatusTransition(appointment.status, status);
8922
+ const updateParams = {
8923
+ appointmentId,
8924
+ clinicId,
8925
+ doctorId: appointment.practitionerProfileId || "",
8926
+ patientId: appointment.patientProfileId || "",
8927
+ status
8928
+ };
8929
+ await this.validateUpdatePermissions(updateParams);
8930
+ return this.updateAppointment(updateParams);
8931
+ }
8932
+ /**
8933
+ * Validates status transition
8934
+ * @param currentStatus - Current status
8935
+ * @param newStatus - New status
8936
+ * @throws Error if transition is invalid
8937
+ */
8938
+ validateStatusTransition(currentStatus, newStatus) {
8939
+ const validTransitions = {
8940
+ ["pending" /* PENDING */]: [
8941
+ "confirmed" /* CONFIRMED */,
8942
+ "rejected" /* REJECTED */,
8943
+ "canceled" /* CANCELED */
8944
+ ],
8945
+ ["confirmed" /* CONFIRMED */]: [
8946
+ "canceled" /* CANCELED */,
8947
+ "completed" /* COMPLETED */,
8948
+ "rescheduled" /* RESCHEDULED */
8949
+ ],
8950
+ ["rejected" /* REJECTED */]: [],
8951
+ ["canceled" /* CANCELED */]: [],
8952
+ ["rescheduled" /* RESCHEDULED */]: [
8953
+ "confirmed" /* CONFIRMED */,
8954
+ "canceled" /* CANCELED */
8955
+ ],
8956
+ ["completed" /* COMPLETED */]: []
8957
+ };
8958
+ if (!validTransitions[currentStatus].includes(newStatus)) {
8959
+ throw new Error(
8960
+ `Invalid status transition from ${currentStatus} to ${newStatus}`
8961
+ );
8962
+ }
8963
+ }
8964
+ /**
8965
+ * Syncs appointment with external calendars based on entity type and status
8966
+ * @param appointment - Calendar event to sync
8967
+ */
8968
+ async syncAppointmentWithExternalCalendars(appointment) {
8969
+ if (!appointment.practitionerProfileId || !appointment.patientProfileId) {
8970
+ return;
8971
+ }
8972
+ try {
8973
+ const [doctorCalendars, patientCalendars] = await Promise.all([
8974
+ this.syncedCalendarsService.getPractitionerSyncedCalendars(
8975
+ appointment.practitionerProfileId
8976
+ ),
8977
+ this.syncedCalendarsService.getPatientSyncedCalendars(
8978
+ appointment.patientProfileId
8979
+ )
8980
+ ]);
8981
+ const activeDoctorCalendars = doctorCalendars.filter(
8982
+ (cal) => cal.isActive
8983
+ );
8984
+ const activePatientCalendars = patientCalendars.filter(
8985
+ (cal) => cal.isActive
8986
+ );
8987
+ if (activeDoctorCalendars.length === 0 && activePatientCalendars.length === 0) {
8988
+ return;
8989
+ }
8990
+ if (appointment.syncStatus !== "internal" /* INTERNAL */) {
8991
+ return;
8992
+ }
8993
+ if (appointment.status === "confirmed" /* CONFIRMED */ && activeDoctorCalendars.length > 0) {
8994
+ await Promise.all(
8995
+ activeDoctorCalendars.map(
8996
+ (calendar) => this.syncEventToExternalCalendar(appointment, calendar, "doctor")
8997
+ )
8998
+ );
8999
+ }
9000
+ if (appointment.status !== "canceled" /* CANCELED */ && appointment.status !== "rejected" /* REJECTED */ && activePatientCalendars.length > 0) {
9001
+ await Promise.all(
9002
+ activePatientCalendars.map(
9003
+ (calendar) => this.syncEventToExternalCalendar(appointment, calendar, "patient")
9004
+ )
9005
+ );
9006
+ }
9007
+ } catch (error) {
9008
+ console.error("Error syncing with external calendars:", error);
9009
+ }
9010
+ }
9011
+ /**
9012
+ * Syncs a single event to an external calendar
9013
+ * @param appointment - Calendar event to sync
9014
+ * @param calendar - External calendar to sync with
9015
+ * @param entityType - Type of entity owning the calendar
9016
+ */
9017
+ async syncEventToExternalCalendar(appointment, calendar, entityType) {
9018
+ var _a, _b, _c, _d, _e;
9019
+ try {
9020
+ const eventToSync = { ...appointment };
9021
+ let eventTitle = appointment.eventName;
9022
+ const clinicName = ((_a = appointment.clinicBranchInfo) == null ? void 0 : _a.name) || "Clinic";
9023
+ if (entityType === "patient") {
9024
+ eventTitle = `[${appointment.status}] ${eventTitle} @ ${clinicName}`;
9025
+ } else {
9026
+ eventTitle = `${eventTitle} - Patient: ${((_b = appointment.patientProfileInfo) == null ? void 0 : _b.fullName) || "Unknown"} @ ${clinicName}`;
9027
+ }
9028
+ eventToSync.eventName = eventTitle;
9029
+ const existingSyncId = (_d = (_c = appointment.syncedCalendarEventId) == null ? void 0 : _c.find(
9030
+ (sync) => sync.syncedCalendarProvider === calendar.provider
9031
+ )) == null ? void 0 : _d.eventId;
9032
+ if (calendar.provider === "google" /* GOOGLE */) {
9033
+ const result = await this.syncedCalendarsService.syncPractitionerEventsToGoogleCalendar(
9034
+ entityType === "doctor" ? appointment.practitionerProfileId : appointment.patientProfileId,
9035
+ calendar.id,
9036
+ [eventToSync],
9037
+ existingSyncId
9038
+ // Pass existing sync ID if we have one
9039
+ );
9040
+ if (result.success && ((_e = result.eventIds) == null ? void 0 : _e.length) && !existingSyncId) {
9041
+ const newSyncEvent = {
9042
+ eventId: result.eventIds[0],
9043
+ syncedCalendarProvider: calendar.provider,
9044
+ syncedAt: Timestamp23.now()
9045
+ };
9046
+ await this.updateEventWithSyncId(
9047
+ entityType === "doctor" ? appointment.practitionerProfileId : appointment.patientProfileId,
9048
+ entityType,
9049
+ appointment.id,
9050
+ newSyncEvent
9051
+ );
9052
+ }
9053
+ }
9054
+ } catch (error) {
9055
+ console.error(`Error syncing with ${entityType}'s calendar:`, error);
9056
+ }
9057
+ }
9058
+ /**
9059
+ * Updates an event with a new sync ID
9060
+ * @param entityId - ID of the entity (doctor or patient)
9061
+ * @param entityType - Type of entity
9062
+ * @param eventId - ID of the event
9063
+ * @param syncEvent - Sync event information
9064
+ */
9065
+ async updateEventWithSyncId(entityId, entityType, eventId, syncEvent) {
9066
+ try {
9067
+ const collectionPath = entityType === "doctor" ? `${PRACTITIONERS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}` : `${PATIENTS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
9068
+ const eventRef = doc19(this.db, collectionPath, eventId);
9069
+ const eventDoc = await getDoc22(eventRef);
9070
+ if (eventDoc.exists()) {
9071
+ const event = eventDoc.data();
9072
+ const syncIds = [...event.syncedCalendarEventId || []];
9073
+ const existingSyncIndex = syncIds.findIndex(
9074
+ (sync) => sync.syncedCalendarProvider === syncEvent.syncedCalendarProvider
9075
+ );
9076
+ if (existingSyncIndex >= 0) {
9077
+ syncIds[existingSyncIndex] = syncEvent;
9078
+ } else {
9079
+ syncIds.push(syncEvent);
9080
+ }
9081
+ await updateDoc20(eventRef, {
9082
+ syncedCalendarEventId: syncIds,
9083
+ updatedAt: serverTimestamp18()
9084
+ });
9085
+ console.log(
9086
+ `Updated event ${eventId} with sync ID ${syncEvent.eventId}`
9087
+ );
9088
+ }
9089
+ } catch (error) {
9090
+ console.error("Error updating event with sync ID:", error);
9091
+ }
9092
+ }
9093
+ /**
9094
+ * Validates update permissions and parameters
9095
+ * @param params - Update parameters to validate
9096
+ */
9097
+ async validateUpdatePermissions(params) {
9098
+ await updateAppointmentSchema.parseAsync(params);
9099
+ }
9100
+ /**
9101
+ * Gets clinic working hours for a specific date
9102
+ * @param clinicId - ID of the clinic
9103
+ * @param date - Date to get working hours for
9104
+ * @returns Working hours for the clinic
9105
+ */
9106
+ async getClinicWorkingHours(clinicId, date) {
9107
+ const clinicRef = doc19(this.db, CLINICS_COLLECTION, clinicId);
9108
+ const clinicDoc = await getDoc22(clinicRef);
9109
+ if (!clinicDoc.exists()) {
9110
+ throw new Error(`Clinic with ID ${clinicId} not found`);
9111
+ }
9112
+ const workingHours = [];
9113
+ const dayOfWeek = date.getDay();
9114
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
9115
+ return workingHours;
9116
+ }
9117
+ const workingDate = new Date(date);
9118
+ workingDate.setHours(9, 0, 0, 0);
9119
+ const startTime = new Date(workingDate);
9120
+ workingDate.setHours(17, 0, 0, 0);
9121
+ const endTime = new Date(workingDate);
9122
+ workingHours.push({
9123
+ start: startTime,
9124
+ end: endTime,
9125
+ isAvailable: true
9126
+ });
9127
+ return workingHours;
9128
+ }
9129
+ /**
9130
+ * Gets doctor's schedule for a specific date
9131
+ * @param doctorId - ID of the doctor
9132
+ * @param date - Date to get schedule for
9133
+ * @returns Doctor's schedule
9134
+ */
9135
+ async getDoctorSchedule(doctorId, date) {
9136
+ const practitionerRef = doc19(this.db, PRACTITIONERS_COLLECTION, doctorId);
9137
+ const practitionerDoc = await getDoc22(practitionerRef);
9138
+ if (!practitionerDoc.exists()) {
9139
+ throw new Error(`Doctor with ID ${doctorId} not found`);
9140
+ }
9141
+ const schedule = [];
9142
+ const dayOfWeek = date.getDay();
9143
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
9144
+ return schedule;
9145
+ }
9146
+ const scheduleDate = new Date(date);
9147
+ scheduleDate.setHours(9, 0, 0, 0);
9148
+ const startTime = new Date(scheduleDate);
9149
+ scheduleDate.setHours(17, 0, 0, 0);
9150
+ const endTime = new Date(scheduleDate);
9151
+ schedule.push({
9152
+ start: startTime,
9153
+ end: endTime,
9154
+ isAvailable: true
9155
+ });
9156
+ return schedule;
9157
+ }
9158
+ /**
9159
+ * Gets doctor's appointments for a specific date
9160
+ * @param doctorId - ID of the doctor
9161
+ * @param date - Date to get appointments for
9162
+ * @returns Array of calendar events
9163
+ */
9164
+ async getDoctorAppointments(doctorId, date) {
9165
+ const startOfDay = new Date(date);
9166
+ startOfDay.setHours(0, 0, 0, 0);
9167
+ const endOfDay = new Date(date);
9168
+ endOfDay.setHours(23, 59, 59, 999);
9169
+ const appointmentsRef = collection17(this.db, CALENDAR_COLLECTION);
9170
+ const q = query16(
9171
+ appointmentsRef,
9172
+ where16("practitionerProfileId", "==", doctorId),
9173
+ where16("eventTime.start", ">=", Timestamp23.fromDate(startOfDay)),
9174
+ where16("eventTime.start", "<=", Timestamp23.fromDate(endOfDay)),
9175
+ where16("status", "in", [
9176
+ "confirmed" /* CONFIRMED */,
9177
+ "pending" /* PENDING */
9178
+ ])
9179
+ );
9180
+ const querySnapshot = await getDocs16(q);
9181
+ return querySnapshot.docs.map((doc20) => doc20.data());
9182
+ }
9183
+ /**
9184
+ * Calculates available time slots based on working hours, schedule and existing appointments
9185
+ * @param workingHours - Clinic working hours
9186
+ * @param doctorSchedule - Doctor's schedule
9187
+ * @param existingAppointments - Existing appointments
9188
+ * @returns Array of available time slots
9189
+ */
9190
+ calculateAvailableSlots(workingHours, doctorSchedule, existingAppointments) {
9191
+ const availableSlots = [];
9192
+ for (const workingHour of workingHours) {
9193
+ for (const scheduleSlot of doctorSchedule) {
9194
+ const overlapStart = new Date(
9195
+ Math.max(workingHour.start.getTime(), scheduleSlot.start.getTime())
9196
+ );
9197
+ const overlapEnd = new Date(
9198
+ Math.min(workingHour.end.getTime(), scheduleSlot.end.getTime())
9199
+ );
9200
+ if (overlapStart < overlapEnd && workingHour.isAvailable && scheduleSlot.isAvailable) {
9201
+ let slotStart = new Date(overlapStart);
9202
+ while (slotStart < overlapEnd) {
9203
+ const slotEnd = new Date(
9204
+ slotStart.getTime() + MIN_APPOINTMENT_DURATION2 * 60 * 1e3
9205
+ );
9206
+ const hasOverlap = existingAppointments.some((appointment) => {
9207
+ const appointmentStart = appointment.eventTime.start.toDate();
9208
+ const appointmentEnd = appointment.eventTime.end.toDate();
9209
+ return slotStart >= appointmentStart && slotStart < appointmentEnd || slotEnd > appointmentStart && slotEnd <= appointmentEnd;
9210
+ });
9211
+ if (!hasOverlap && slotEnd <= overlapEnd) {
9212
+ availableSlots.push({
9213
+ start: new Date(slotStart),
9214
+ end: new Date(slotEnd),
9215
+ isAvailable: true
9216
+ });
9217
+ }
9218
+ slotStart = new Date(
9219
+ slotStart.getTime() + MIN_APPOINTMENT_DURATION2 * 60 * 1e3
9220
+ );
9221
+ }
9222
+ }
9223
+ }
9224
+ }
9225
+ return availableSlots;
9226
+ }
9227
+ /**
9228
+ * Fetches and creates info cards for clinic, doctor, and patient profiles
9229
+ * @param clinicId - ID of the clinic
9230
+ * @param doctorId - ID of the doctor
9231
+ * @param patientId - ID of the patient
9232
+ * @returns Object containing info cards for all profiles
9233
+ */
9234
+ async fetchProfileInfoCards(clinicId, doctorId, patientId) {
9235
+ var _a;
9236
+ try {
9237
+ const [clinicDoc, practitionerDoc, patientDoc, patientSensitiveInfoDoc] = await Promise.all([
9238
+ getDoc22(doc19(this.db, CLINICS_COLLECTION, clinicId)),
9239
+ getDoc22(doc19(this.db, PRACTITIONERS_COLLECTION, doctorId)),
9240
+ getDoc22(doc19(this.db, PATIENTS_COLLECTION, patientId)),
9241
+ getDoc22(
9242
+ doc19(
9243
+ this.db,
9244
+ PATIENTS_COLLECTION,
9245
+ patientId,
9246
+ PATIENT_SENSITIVE_INFO_COLLECTION,
9247
+ patientId
9248
+ )
9249
+ )
9250
+ ]);
9251
+ const clinicInfo = clinicDoc.exists() ? {
9252
+ id: clinicDoc.id,
9253
+ featuredPhoto: clinicDoc.data().featuredPhoto || "",
9254
+ name: clinicDoc.data().name,
9255
+ description: clinicDoc.data().description || "",
9256
+ location: clinicDoc.data().location,
9257
+ contactInfo: clinicDoc.data().contactInfo
9258
+ } : null;
9259
+ const practitionerInfo = practitionerDoc.exists() ? {
9260
+ id: practitionerDoc.id,
9261
+ practitionerPhoto: practitionerDoc.data().basicInfo.profileImageUrl || null,
9262
+ name: `${practitionerDoc.data().basicInfo.firstName} ${practitionerDoc.data().basicInfo.lastName}`,
9263
+ email: practitionerDoc.data().basicInfo.email,
9264
+ phone: practitionerDoc.data().basicInfo.phoneNumber || null,
9265
+ certification: practitionerDoc.data().certification
9266
+ } : null;
9267
+ let patientInfo = null;
9268
+ if (patientSensitiveInfoDoc.exists()) {
9269
+ const sensitiveData = patientSensitiveInfoDoc.data();
9270
+ patientInfo = {
9271
+ id: patientId,
9272
+ fullName: `${sensitiveData.firstName} ${sensitiveData.lastName}`,
9273
+ email: sensitiveData.email || "",
9274
+ phone: sensitiveData.phoneNumber || null,
9275
+ dateOfBirth: sensitiveData.dateOfBirth || Timestamp23.now(),
9276
+ gender: sensitiveData.gender || "other" /* OTHER */
9277
+ };
9278
+ } else if (patientDoc.exists()) {
9279
+ patientInfo = {
9280
+ id: patientDoc.id,
9281
+ fullName: patientDoc.data().displayName,
9282
+ email: ((_a = patientDoc.data().contactInfo) == null ? void 0 : _a.email) || "",
9283
+ phone: patientDoc.data().phoneNumber || null,
9284
+ dateOfBirth: patientDoc.data().dateOfBirth || Timestamp23.now(),
9285
+ gender: patientDoc.data().gender || "other" /* OTHER */
9286
+ };
9287
+ }
9288
+ return {
9289
+ clinicInfo,
9290
+ practitionerInfo,
9291
+ patientInfo
9292
+ };
9293
+ } catch (error) {
9294
+ console.error("Error fetching profile info cards:", error);
9295
+ return {
9296
+ clinicInfo: null,
9297
+ practitionerInfo: null,
9298
+ patientInfo: null
9299
+ };
9300
+ }
9301
+ }
9302
+ // #endregion
9303
+ };
9304
+
9305
+ // src/validations/notification.schema.ts
9306
+ import { z as z18 } from "zod";
9307
+ var baseNotificationSchema = z18.object({
9308
+ id: z18.string().optional(),
9309
+ userId: z18.string(),
9310
+ notificationTime: z18.any(),
6390
9311
  // Timestamp
6391
- notificationType: z16.nativeEnum(NotificationType),
6392
- notificationTokens: z16.array(z16.string()),
6393
- status: z16.nativeEnum(NotificationStatus),
6394
- createdAt: z16.any().optional(),
9312
+ notificationType: z18.nativeEnum(NotificationType),
9313
+ notificationTokens: z18.array(z18.string()),
9314
+ status: z18.nativeEnum(NotificationStatus),
9315
+ createdAt: z18.any().optional(),
6395
9316
  // Timestamp
6396
- updatedAt: z16.any().optional(),
9317
+ updatedAt: z18.any().optional(),
6397
9318
  // Timestamp
6398
- title: z16.string(),
6399
- body: z16.string(),
6400
- isRead: z16.boolean(),
6401
- userRole: z16.nativeEnum(UserRole)
9319
+ title: z18.string(),
9320
+ body: z18.string(),
9321
+ isRead: z18.boolean(),
9322
+ userRole: z18.nativeEnum(UserRole)
6402
9323
  });
6403
9324
  var preRequirementNotificationSchema = baseNotificationSchema.extend({
6404
- notificationType: z16.literal("preRequirement" /* PRE_REQUIREMENT */),
6405
- treatmentId: z16.string(),
6406
- requirements: z16.array(z16.string()),
6407
- deadline: z16.any()
9325
+ notificationType: z18.literal("preRequirement" /* PRE_REQUIREMENT */),
9326
+ treatmentId: z18.string(),
9327
+ requirements: z18.array(z18.string()),
9328
+ deadline: z18.any()
6408
9329
  // Timestamp
6409
9330
  });
6410
9331
  var postRequirementNotificationSchema = baseNotificationSchema.extend({
6411
- notificationType: z16.literal("postRequirement" /* POST_REQUIREMENT */),
6412
- treatmentId: z16.string(),
6413
- requirements: z16.array(z16.string()),
6414
- deadline: z16.any()
9332
+ notificationType: z18.literal("postRequirement" /* POST_REQUIREMENT */),
9333
+ treatmentId: z18.string(),
9334
+ requirements: z18.array(z18.string()),
9335
+ deadline: z18.any()
6415
9336
  // Timestamp
6416
9337
  });
6417
9338
  var appointmentReminderNotificationSchema = baseNotificationSchema.extend({
6418
- notificationType: z16.literal("appointmentReminder" /* APPOINTMENT_REMINDER */),
6419
- appointmentId: z16.string(),
6420
- appointmentTime: z16.any(),
9339
+ notificationType: z18.literal("appointmentReminder" /* APPOINTMENT_REMINDER */),
9340
+ appointmentId: z18.string(),
9341
+ appointmentTime: z18.any(),
6421
9342
  // Timestamp
6422
- treatmentType: z16.string(),
6423
- doctorName: z16.string()
9343
+ treatmentType: z18.string(),
9344
+ doctorName: z18.string()
6424
9345
  });
6425
9346
  var appointmentNotificationSchema = baseNotificationSchema.extend({
6426
- notificationType: z16.literal("appointmentNotification" /* APPOINTMENT_NOTIFICATION */),
6427
- appointmentId: z16.string(),
6428
- appointmentStatus: z16.string(),
6429
- previousStatus: z16.string(),
6430
- reason: z16.string().optional()
9347
+ notificationType: z18.literal("appointmentNotification" /* APPOINTMENT_NOTIFICATION */),
9348
+ appointmentId: z18.string(),
9349
+ appointmentStatus: z18.string(),
9350
+ previousStatus: z18.string(),
9351
+ reason: z18.string().optional()
6431
9352
  });
6432
- var notificationSchema = z16.discriminatedUnion("notificationType", [
9353
+ var notificationSchema = z18.discriminatedUnion("notificationType", [
6433
9354
  preRequirementNotificationSchema,
6434
9355
  postRequirementNotificationSchema,
6435
9356
  appointmentReminderNotificationSchema,
@@ -6441,9 +9362,14 @@ export {
6441
9362
  AllergyType,
6442
9363
  AuthService,
6443
9364
  BlockingCondition,
9365
+ CALENDAR_COLLECTION,
6444
9366
  CLINICS_COLLECTION,
6445
9367
  CLINIC_ADMINS_COLLECTION,
6446
9368
  CLINIC_GROUPS_COLLECTION,
9369
+ CalendarEventStatus,
9370
+ CalendarEventType,
9371
+ CalendarServiceV2,
9372
+ CalendarSyncStatus,
6447
9373
  CertificationLevel,
6448
9374
  CertificationSpecialty,
6449
9375
  ClinicAdminService,
@@ -6482,9 +9408,15 @@ export {
6482
9408
  PatientService,
6483
9409
  PracticeType,
6484
9410
  PractitionerService,
9411
+ PractitionerStatus,
9412
+ PractitionerTokenStatus,
6485
9413
  PricingMeasure,
6486
9414
  ProcedureFamily,
9415
+ REGISTER_TOKENS_COLLECTION,
9416
+ SYNCED_CALENDARS_COLLECTION,
6487
9417
  SubscriptionModel,
9418
+ SyncedCalendarProvider,
9419
+ SyncedCalendarsService,
6488
9420
  TreatmentBenefit,
6489
9421
  USER_ERRORS,
6490
9422
  UserService,
@@ -6501,6 +9433,8 @@ export {
6501
9433
  appointmentReminderNotificationSchema,
6502
9434
  baseNotificationSchema,
6503
9435
  blockingConditionSchema,
9436
+ calendarEventSchema,
9437
+ calendarEventTimeSchema,
6504
9438
  clinicAdminOptionsSchema,
6505
9439
  clinicAdminSchema,
6506
9440
  clinicAdminSignupSchema,
@@ -6516,16 +9450,20 @@ export {
6516
9450
  contactPersonSchema,
6517
9451
  contraindicationSchema,
6518
9452
  createAdminTokenSchema,
9453
+ createAppointmentSchema,
9454
+ createCalendarEventSchema,
6519
9455
  createClinicAdminSchema,
6520
9456
  createClinicGroupSchema,
6521
9457
  createClinicSchema,
6522
9458
  createDefaultClinicGroupSchema,
6523
9459
  createDocumentTemplateSchema,
9460
+ createDraftPractitionerSchema,
6524
9461
  createPatientLocationInfoSchema,
6525
9462
  createPatientMedicalInfoSchema,
6526
9463
  createPatientProfileSchema,
6527
9464
  createPatientSensitiveInfoSchema,
6528
9465
  createPractitionerSchema,
9466
+ createPractitionerTokenSchema,
6529
9467
  createUserOptionsSchema,
6530
9468
  doctorInfoSchema,
6531
9469
  documentElementSchema,
@@ -6547,21 +9485,31 @@ export {
6547
9485
  patientDoctorSchema,
6548
9486
  patientLocationInfoSchema,
6549
9487
  patientMedicalInfoSchema,
9488
+ patientProfileInfoSchema,
6550
9489
  patientProfileSchema,
6551
9490
  patientSensitiveInfoSchema,
6552
9491
  postRequirementNotificationSchema,
6553
9492
  practitionerBasicInfoSchema,
6554
9493
  practitionerCertificationSchema,
6555
9494
  practitionerClinicProceduresSchema,
9495
+ practitionerClinicWorkingHoursSchema,
9496
+ practitionerProfileInfoSchema,
6556
9497
  practitionerReviewSchema,
6557
9498
  practitionerSchema,
9499
+ practitionerTokenSchema,
6558
9500
  practitionerWorkingHoursSchema,
6559
9501
  preRequirementNotificationSchema,
9502
+ procedureCategorizationSchema,
9503
+ procedureInfoSchema,
6560
9504
  reviewInfoSchema,
6561
9505
  serviceInfoSchema,
9506
+ syncedCalendarEventSchema,
9507
+ timeSlotSchema2 as timeSlotSchema,
6562
9508
  timestampSchema,
6563
9509
  updateAllergySchema,
9510
+ updateAppointmentSchema,
6564
9511
  updateBlockingConditionSchema,
9512
+ updateCalendarEventSchema,
6565
9513
  updateClinicAdminSchema,
6566
9514
  updateClinicGroupSchema,
6567
9515
  updateClinicSchema,