@blackcode_sa/metaestetics-api 1.4.18 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +7405 -4795
- package/dist/index.d.ts +7405 -4795
- package/dist/index.js +2736 -109
- package/dist/index.mjs +2772 -109
- package/package.json +4 -3
- package/src/index.ts +48 -1
- package/src/services/calendar/calendar-refactored.service.ts +1531 -0
- package/src/services/calendar/calendar.service.ts +1077 -0
- package/src/services/calendar/synced-calendars.service.ts +743 -0
- package/src/services/calendar/utils/appointment.utils.ts +314 -0
- package/src/services/calendar/utils/calendar-event.utils.ts +510 -0
- package/src/services/calendar/utils/clinic.utils.ts +237 -0
- package/src/services/calendar/utils/docs.utils.ts +157 -0
- package/src/services/calendar/utils/google-calendar.utils.ts +697 -0
- package/src/services/calendar/utils/index.ts +8 -0
- package/src/services/calendar/utils/patient.utils.ts +198 -0
- package/src/services/calendar/utils/practitioner.utils.ts +221 -0
- package/src/services/calendar/utils/synced-calendar.utils.ts +472 -0
- package/src/services/practitioner/practitioner.service.ts +1 -0
- package/src/types/calendar/index.ts +187 -0
- package/src/types/calendar/synced-calendar.types.ts +66 -0
- package/src/types/clinic/index.ts +1 -12
- package/src/types/index.ts +4 -0
- package/src/types/practitioner/index.ts +21 -0
- package/src/types/profile/index.ts +39 -0
- package/src/validations/calendar.schema.ts +223 -0
- package/src/validations/practitioner.schema.ts +21 -0
- package/src/validations/profile-info.schema.ts +41 -0
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
|
|
1280
|
-
if (!
|
|
1281
|
-
const medicalInfo =
|
|
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
|
|
1298
|
-
if (!
|
|
1299
|
-
const medicalInfo =
|
|
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
|
|
1325
|
-
if (!
|
|
1326
|
-
const medicalInfo =
|
|
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
|
|
1343
|
-
if (!
|
|
1344
|
-
const medicalInfo =
|
|
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
|
|
1370
|
-
if (!
|
|
1371
|
-
const medicalInfo =
|
|
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
|
|
1388
|
-
if (!
|
|
1389
|
-
const medicalInfo =
|
|
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
|
|
1415
|
-
if (!
|
|
1416
|
-
const medicalInfo =
|
|
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
|
|
1433
|
-
if (!
|
|
1434
|
-
const medicalInfo =
|
|
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__ */ ((
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
return
|
|
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((
|
|
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);
|
|
@@ -3096,6 +3121,21 @@ var practitionerWorkingHoursSchema = z10.object({
|
|
|
3096
3121
|
createdAt: z10.instanceof(Timestamp7),
|
|
3097
3122
|
updatedAt: z10.instanceof(Timestamp7)
|
|
3098
3123
|
});
|
|
3124
|
+
var practitionerClinicWorkingHoursSchema = z10.object({
|
|
3125
|
+
clinicId: z10.string().min(1),
|
|
3126
|
+
workingHours: z10.object({
|
|
3127
|
+
monday: timeSlotSchema,
|
|
3128
|
+
tuesday: timeSlotSchema,
|
|
3129
|
+
wednesday: timeSlotSchema,
|
|
3130
|
+
thursday: timeSlotSchema,
|
|
3131
|
+
friday: timeSlotSchema,
|
|
3132
|
+
saturday: timeSlotSchema,
|
|
3133
|
+
sunday: timeSlotSchema
|
|
3134
|
+
}),
|
|
3135
|
+
isActive: z10.boolean(),
|
|
3136
|
+
createdAt: z10.instanceof(Timestamp7),
|
|
3137
|
+
updatedAt: z10.instanceof(Timestamp7)
|
|
3138
|
+
});
|
|
3099
3139
|
var practitionerReviewSchema = z10.object({
|
|
3100
3140
|
id: z10.string().min(1),
|
|
3101
3141
|
practitionerId: z10.string().min(1),
|
|
@@ -3121,6 +3161,7 @@ var practitionerSchema = z10.object({
|
|
|
3121
3161
|
basicInfo: practitionerBasicInfoSchema,
|
|
3122
3162
|
certification: practitionerCertificationSchema,
|
|
3123
3163
|
clinics: z10.array(z10.string()),
|
|
3164
|
+
clinicWorkingHours: z10.array(practitionerClinicWorkingHoursSchema),
|
|
3124
3165
|
isActive: z10.boolean(),
|
|
3125
3166
|
isVerified: z10.boolean(),
|
|
3126
3167
|
createdAt: z10.instanceof(Timestamp7),
|
|
@@ -3131,6 +3172,7 @@ var createPractitionerSchema = z10.object({
|
|
|
3131
3172
|
basicInfo: practitionerBasicInfoSchema,
|
|
3132
3173
|
certification: practitionerCertificationSchema,
|
|
3133
3174
|
clinics: z10.array(z10.string()).optional(),
|
|
3175
|
+
clinicWorkingHours: z10.array(practitionerClinicWorkingHoursSchema).optional(),
|
|
3134
3176
|
isActive: z10.boolean(),
|
|
3135
3177
|
isVerified: z10.boolean()
|
|
3136
3178
|
});
|
|
@@ -3180,6 +3222,7 @@ var PractitionerService = class extends BaseService {
|
|
|
3180
3222
|
basicInfo: validatedData.basicInfo,
|
|
3181
3223
|
certification: validatedData.certification,
|
|
3182
3224
|
clinics: validatedData.clinics || [],
|
|
3225
|
+
clinicWorkingHours: validatedData.clinicWorkingHours || [],
|
|
3183
3226
|
isActive: validatedData.isActive,
|
|
3184
3227
|
isVerified: validatedData.isVerified,
|
|
3185
3228
|
createdAt: serverTimestamp9(),
|
|
@@ -3242,7 +3285,7 @@ var PractitionerService = class extends BaseService {
|
|
|
3242
3285
|
where3("isActive", "==", true)
|
|
3243
3286
|
);
|
|
3244
3287
|
const querySnapshot = await getDocs3(q);
|
|
3245
|
-
return querySnapshot.docs.map((
|
|
3288
|
+
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
3246
3289
|
}
|
|
3247
3290
|
/**
|
|
3248
3291
|
* Ažurira profil zdravstvenog radnika
|
|
@@ -3522,7 +3565,7 @@ var UserService = class extends BaseService {
|
|
|
3522
3565
|
];
|
|
3523
3566
|
const q = query4(collection4(this.db, USERS_COLLECTION), ...constraints);
|
|
3524
3567
|
const querySnapshot = await getDocs4(q);
|
|
3525
|
-
const users = querySnapshot.docs.map((
|
|
3568
|
+
const users = querySnapshot.docs.map((doc20) => doc20.data());
|
|
3526
3569
|
return Promise.all(users.map((userData) => userSchema.parse(userData)));
|
|
3527
3570
|
}
|
|
3528
3571
|
/**
|
|
@@ -3921,7 +3964,7 @@ async function getAllActiveGroups(db) {
|
|
|
3921
3964
|
where5("isActive", "==", true)
|
|
3922
3965
|
);
|
|
3923
3966
|
const querySnapshot = await getDocs5(q);
|
|
3924
|
-
return querySnapshot.docs.map((
|
|
3967
|
+
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
3925
3968
|
}
|
|
3926
3969
|
async function updateClinicGroup(db, groupId, data, app) {
|
|
3927
3970
|
console.log("[CLINIC_GROUP] Updating clinic group", { groupId });
|
|
@@ -4591,7 +4634,7 @@ async function getClinicsByGroup(db, groupId) {
|
|
|
4591
4634
|
where6("isActive", "==", true)
|
|
4592
4635
|
);
|
|
4593
4636
|
const querySnapshot = await getDocs6(q);
|
|
4594
|
-
return querySnapshot.docs.map((
|
|
4637
|
+
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
4595
4638
|
}
|
|
4596
4639
|
async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app) {
|
|
4597
4640
|
console.log("[CLINIC] Starting clinic update", { clinicId, adminId });
|
|
@@ -4803,7 +4846,7 @@ async function getClinicsByAdmin(db, adminId, options = {}, clinicAdminService,
|
|
|
4803
4846
|
}
|
|
4804
4847
|
const q = query6(collection6(db, CLINICS_COLLECTION), ...constraints);
|
|
4805
4848
|
const querySnapshot = await getDocs6(q);
|
|
4806
|
-
return querySnapshot.docs.map((
|
|
4849
|
+
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
4807
4850
|
}
|
|
4808
4851
|
async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGroupService) {
|
|
4809
4852
|
return getClinicsByAdmin(
|
|
@@ -4956,8 +4999,8 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
|
|
|
4956
4999
|
}
|
|
4957
5000
|
const q = query7(collection8(db, CLINICS_COLLECTION), ...constraints);
|
|
4958
5001
|
const querySnapshot = await getDocs7(q);
|
|
4959
|
-
for (const
|
|
4960
|
-
const clinic =
|
|
5002
|
+
for (const doc20 of querySnapshot.docs) {
|
|
5003
|
+
const clinic = doc20.data();
|
|
4961
5004
|
const distance = distanceBetween(
|
|
4962
5005
|
[center.latitude, center.longitude],
|
|
4963
5006
|
[clinic.location.latitude, clinic.location.longitude]
|
|
@@ -5831,9 +5874,9 @@ var NotificationService = class extends BaseService {
|
|
|
5831
5874
|
orderBy("notificationTime", "desc")
|
|
5832
5875
|
);
|
|
5833
5876
|
const querySnapshot = await getDocs9(q);
|
|
5834
|
-
return querySnapshot.docs.map((
|
|
5835
|
-
id:
|
|
5836
|
-
...
|
|
5877
|
+
return querySnapshot.docs.map((doc20) => ({
|
|
5878
|
+
id: doc20.id,
|
|
5879
|
+
...doc20.data()
|
|
5837
5880
|
}));
|
|
5838
5881
|
}
|
|
5839
5882
|
/**
|
|
@@ -5847,9 +5890,9 @@ var NotificationService = class extends BaseService {
|
|
|
5847
5890
|
orderBy("notificationTime", "desc")
|
|
5848
5891
|
);
|
|
5849
5892
|
const querySnapshot = await getDocs9(q);
|
|
5850
|
-
return querySnapshot.docs.map((
|
|
5851
|
-
id:
|
|
5852
|
-
...
|
|
5893
|
+
return querySnapshot.docs.map((doc20) => ({
|
|
5894
|
+
id: doc20.id,
|
|
5895
|
+
...doc20.data()
|
|
5853
5896
|
}));
|
|
5854
5897
|
}
|
|
5855
5898
|
/**
|
|
@@ -5921,9 +5964,9 @@ var NotificationService = class extends BaseService {
|
|
|
5921
5964
|
orderBy("notificationTime", "desc")
|
|
5922
5965
|
);
|
|
5923
5966
|
const querySnapshot = await getDocs9(q);
|
|
5924
|
-
return querySnapshot.docs.map((
|
|
5925
|
-
id:
|
|
5926
|
-
...
|
|
5967
|
+
return querySnapshot.docs.map((doc20) => ({
|
|
5968
|
+
id: doc20.id,
|
|
5969
|
+
...doc20.data()
|
|
5927
5970
|
}));
|
|
5928
5971
|
}
|
|
5929
5972
|
/**
|
|
@@ -5936,9 +5979,9 @@ var NotificationService = class extends BaseService {
|
|
|
5936
5979
|
orderBy("notificationTime", "desc")
|
|
5937
5980
|
);
|
|
5938
5981
|
const querySnapshot = await getDocs9(q);
|
|
5939
|
-
return querySnapshot.docs.map((
|
|
5940
|
-
id:
|
|
5941
|
-
...
|
|
5982
|
+
return querySnapshot.docs.map((doc20) => ({
|
|
5983
|
+
id: doc20.id,
|
|
5984
|
+
...doc20.data()
|
|
5942
5985
|
}));
|
|
5943
5986
|
}
|
|
5944
5987
|
};
|
|
@@ -6068,9 +6111,9 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
6068
6111
|
const querySnapshot = await getDocs10(q);
|
|
6069
6112
|
const templates = [];
|
|
6070
6113
|
let lastVisible = null;
|
|
6071
|
-
querySnapshot.forEach((
|
|
6072
|
-
templates.push(
|
|
6073
|
-
lastVisible =
|
|
6114
|
+
querySnapshot.forEach((doc20) => {
|
|
6115
|
+
templates.push(doc20.data());
|
|
6116
|
+
lastVisible = doc20;
|
|
6074
6117
|
});
|
|
6075
6118
|
return {
|
|
6076
6119
|
templates,
|
|
@@ -6098,9 +6141,9 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
6098
6141
|
const querySnapshot = await getDocs10(q);
|
|
6099
6142
|
const templates = [];
|
|
6100
6143
|
let lastVisible = null;
|
|
6101
|
-
querySnapshot.forEach((
|
|
6102
|
-
templates.push(
|
|
6103
|
-
lastVisible =
|
|
6144
|
+
querySnapshot.forEach((doc20) => {
|
|
6145
|
+
templates.push(doc20.data());
|
|
6146
|
+
lastVisible = doc20;
|
|
6104
6147
|
});
|
|
6105
6148
|
return {
|
|
6106
6149
|
templates,
|
|
@@ -6127,9 +6170,9 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
6127
6170
|
const querySnapshot = await getDocs10(q);
|
|
6128
6171
|
const templates = [];
|
|
6129
6172
|
let lastVisible = null;
|
|
6130
|
-
querySnapshot.forEach((
|
|
6131
|
-
templates.push(
|
|
6132
|
-
lastVisible =
|
|
6173
|
+
querySnapshot.forEach((doc20) => {
|
|
6174
|
+
templates.push(doc20.data());
|
|
6175
|
+
lastVisible = doc20;
|
|
6133
6176
|
});
|
|
6134
6177
|
return {
|
|
6135
6178
|
templates,
|
|
@@ -6254,9 +6297,9 @@ var FilledDocumentService = class extends BaseService {
|
|
|
6254
6297
|
const querySnapshot = await getDocs11(q);
|
|
6255
6298
|
const documents = [];
|
|
6256
6299
|
let lastVisible = null;
|
|
6257
|
-
querySnapshot.forEach((
|
|
6258
|
-
documents.push(
|
|
6259
|
-
lastVisible =
|
|
6300
|
+
querySnapshot.forEach((doc20) => {
|
|
6301
|
+
documents.push(doc20.data());
|
|
6302
|
+
lastVisible = doc20;
|
|
6260
6303
|
});
|
|
6261
6304
|
return {
|
|
6262
6305
|
documents,
|
|
@@ -6283,9 +6326,9 @@ var FilledDocumentService = class extends BaseService {
|
|
|
6283
6326
|
const querySnapshot = await getDocs11(q);
|
|
6284
6327
|
const documents = [];
|
|
6285
6328
|
let lastVisible = null;
|
|
6286
|
-
querySnapshot.forEach((
|
|
6287
|
-
documents.push(
|
|
6288
|
-
lastVisible =
|
|
6329
|
+
querySnapshot.forEach((doc20) => {
|
|
6330
|
+
documents.push(doc20.data());
|
|
6331
|
+
lastVisible = doc20;
|
|
6289
6332
|
});
|
|
6290
6333
|
return {
|
|
6291
6334
|
documents,
|
|
@@ -6312,9 +6355,9 @@ var FilledDocumentService = class extends BaseService {
|
|
|
6312
6355
|
const querySnapshot = await getDocs11(q);
|
|
6313
6356
|
const documents = [];
|
|
6314
6357
|
let lastVisible = null;
|
|
6315
|
-
querySnapshot.forEach((
|
|
6316
|
-
documents.push(
|
|
6317
|
-
lastVisible =
|
|
6358
|
+
querySnapshot.forEach((doc20) => {
|
|
6359
|
+
documents.push(doc20.data());
|
|
6360
|
+
lastVisible = doc20;
|
|
6318
6361
|
});
|
|
6319
6362
|
return {
|
|
6320
6363
|
documents,
|
|
@@ -6341,9 +6384,9 @@ var FilledDocumentService = class extends BaseService {
|
|
|
6341
6384
|
const querySnapshot = await getDocs11(q);
|
|
6342
6385
|
const documents = [];
|
|
6343
6386
|
let lastVisible = null;
|
|
6344
|
-
querySnapshot.forEach((
|
|
6345
|
-
documents.push(
|
|
6346
|
-
lastVisible =
|
|
6387
|
+
querySnapshot.forEach((doc20) => {
|
|
6388
|
+
documents.push(doc20.data());
|
|
6389
|
+
lastVisible = doc20;
|
|
6347
6390
|
});
|
|
6348
6391
|
return {
|
|
6349
6392
|
documents,
|
|
@@ -6370,9 +6413,9 @@ var FilledDocumentService = class extends BaseService {
|
|
|
6370
6413
|
const querySnapshot = await getDocs11(q);
|
|
6371
6414
|
const documents = [];
|
|
6372
6415
|
let lastVisible = null;
|
|
6373
|
-
querySnapshot.forEach((
|
|
6374
|
-
documents.push(
|
|
6375
|
-
lastVisible =
|
|
6416
|
+
querySnapshot.forEach((doc20) => {
|
|
6417
|
+
documents.push(doc20.data());
|
|
6418
|
+
lastVisible = doc20;
|
|
6376
6419
|
});
|
|
6377
6420
|
return {
|
|
6378
6421
|
documents,
|
|
@@ -6381,55 +6424,2654 @@ var FilledDocumentService = class extends BaseService {
|
|
|
6381
6424
|
}
|
|
6382
6425
|
};
|
|
6383
6426
|
|
|
6384
|
-
// src/
|
|
6427
|
+
// src/services/calendar/calendar-refactored.service.ts
|
|
6428
|
+
import { Timestamp as Timestamp23, serverTimestamp as serverTimestamp18 } from "firebase/firestore";
|
|
6429
|
+
|
|
6430
|
+
// src/types/calendar/synced-calendar.types.ts
|
|
6431
|
+
var SyncedCalendarProvider = /* @__PURE__ */ ((SyncedCalendarProvider3) => {
|
|
6432
|
+
SyncedCalendarProvider3["GOOGLE"] = "google";
|
|
6433
|
+
SyncedCalendarProvider3["OUTLOOK"] = "outlook";
|
|
6434
|
+
SyncedCalendarProvider3["APPLE"] = "apple";
|
|
6435
|
+
return SyncedCalendarProvider3;
|
|
6436
|
+
})(SyncedCalendarProvider || {});
|
|
6437
|
+
var SYNCED_CALENDARS_COLLECTION = "syncedCalendars";
|
|
6438
|
+
|
|
6439
|
+
// src/services/calendar/calendar-refactored.service.ts
|
|
6440
|
+
import {
|
|
6441
|
+
doc as doc19,
|
|
6442
|
+
getDoc as getDoc22,
|
|
6443
|
+
collection as collection17,
|
|
6444
|
+
query as query16,
|
|
6445
|
+
where as where16,
|
|
6446
|
+
getDocs as getDocs16,
|
|
6447
|
+
setDoc as setDoc19,
|
|
6448
|
+
updateDoc as updateDoc20
|
|
6449
|
+
} from "firebase/firestore";
|
|
6450
|
+
|
|
6451
|
+
// src/validations/calendar.schema.ts
|
|
6452
|
+
import { z as z17 } from "zod";
|
|
6453
|
+
import { Timestamp as Timestamp17 } from "firebase/firestore";
|
|
6454
|
+
|
|
6455
|
+
// src/validations/profile-info.schema.ts
|
|
6385
6456
|
import { z as z16 } from "zod";
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6457
|
+
import { Timestamp as Timestamp16 } from "firebase/firestore";
|
|
6458
|
+
var clinicInfoSchema2 = z16.object({
|
|
6459
|
+
id: z16.string(),
|
|
6460
|
+
featuredPhoto: z16.string(),
|
|
6461
|
+
name: z16.string(),
|
|
6462
|
+
description: z16.string(),
|
|
6463
|
+
location: clinicLocationSchema,
|
|
6464
|
+
contactInfo: clinicContactInfoSchema
|
|
6465
|
+
});
|
|
6466
|
+
var practitionerProfileInfoSchema = z16.object({
|
|
6467
|
+
id: z16.string(),
|
|
6468
|
+
practitionerPhoto: z16.string().nullable(),
|
|
6469
|
+
name: z16.string(),
|
|
6470
|
+
email: z16.string().email(),
|
|
6471
|
+
phone: z16.string().nullable(),
|
|
6472
|
+
certification: practitionerCertificationSchema
|
|
6473
|
+
});
|
|
6474
|
+
var patientProfileInfoSchema = z16.object({
|
|
6475
|
+
id: z16.string(),
|
|
6476
|
+
fullName: z16.string(),
|
|
6477
|
+
email: z16.string().email(),
|
|
6478
|
+
phone: z16.string().nullable(),
|
|
6479
|
+
dateOfBirth: z16.instanceof(Timestamp16),
|
|
6480
|
+
gender: z16.nativeEnum(Gender)
|
|
6481
|
+
});
|
|
6482
|
+
|
|
6483
|
+
// src/validations/calendar.schema.ts
|
|
6484
|
+
var MIN_APPOINTMENT_DURATION = 15;
|
|
6485
|
+
var calendarEventTimeSchema = z17.object({
|
|
6486
|
+
start: z17.instanceof(Date).or(z17.instanceof(Timestamp17)),
|
|
6487
|
+
end: z17.instanceof(Date).or(z17.instanceof(Timestamp17))
|
|
6488
|
+
}).refine(
|
|
6489
|
+
(data) => {
|
|
6490
|
+
const startDate = data.start instanceof Timestamp17 ? data.start.toDate() : data.start;
|
|
6491
|
+
const endDate = data.end instanceof Timestamp17 ? data.end.toDate() : data.end;
|
|
6492
|
+
return startDate < endDate;
|
|
6493
|
+
},
|
|
6494
|
+
{
|
|
6495
|
+
message: "End time must be after start time",
|
|
6496
|
+
path: ["end"]
|
|
6497
|
+
}
|
|
6498
|
+
).refine(
|
|
6499
|
+
(data) => {
|
|
6500
|
+
const startDate = data.start instanceof Timestamp17 ? data.start.toDate() : data.start;
|
|
6501
|
+
return startDate > /* @__PURE__ */ new Date();
|
|
6502
|
+
},
|
|
6503
|
+
{
|
|
6504
|
+
message: "Appointment must be scheduled in the future",
|
|
6505
|
+
path: ["start"]
|
|
6506
|
+
}
|
|
6507
|
+
);
|
|
6508
|
+
var timeSlotSchema2 = z17.object({
|
|
6509
|
+
start: z17.date(),
|
|
6510
|
+
end: z17.date(),
|
|
6511
|
+
isAvailable: z17.boolean()
|
|
6512
|
+
}).refine((data) => data.start < data.end, {
|
|
6513
|
+
message: "End time must be after start time",
|
|
6514
|
+
path: ["end"]
|
|
6515
|
+
});
|
|
6516
|
+
var syncedCalendarEventSchema = z17.object({
|
|
6517
|
+
eventId: z17.string(),
|
|
6518
|
+
syncedCalendarProvider: z17.nativeEnum(SyncedCalendarProvider),
|
|
6519
|
+
syncedAt: z17.instanceof(Date).or(z17.instanceof(Timestamp17))
|
|
6520
|
+
});
|
|
6521
|
+
var procedureInfoSchema = z17.object({
|
|
6522
|
+
name: z17.string(),
|
|
6523
|
+
description: z17.string(),
|
|
6524
|
+
duration: z17.number().min(MIN_APPOINTMENT_DURATION),
|
|
6525
|
+
price: z17.number().min(0),
|
|
6526
|
+
currency: z17.nativeEnum(Currency)
|
|
6527
|
+
});
|
|
6528
|
+
var procedureCategorizationSchema = z17.object({
|
|
6529
|
+
procedureFamily: z17.string(),
|
|
6530
|
+
// Replace with proper enum when available
|
|
6531
|
+
procedureCategory: z17.string(),
|
|
6532
|
+
// Replace with proper enum when available
|
|
6533
|
+
procedureSubcategory: z17.string(),
|
|
6534
|
+
// Replace with proper enum when available
|
|
6535
|
+
procedureTechnology: z17.string(),
|
|
6536
|
+
// Replace with proper enum when available
|
|
6537
|
+
procedureProduct: z17.string()
|
|
6538
|
+
// Replace with proper enum when available
|
|
6539
|
+
});
|
|
6540
|
+
var createAppointmentSchema = z17.object({
|
|
6541
|
+
clinicId: z17.string().min(1, "Clinic ID is required"),
|
|
6542
|
+
doctorId: z17.string().min(1, "Doctor ID is required"),
|
|
6543
|
+
patientId: z17.string().min(1, "Patient ID is required"),
|
|
6544
|
+
procedureId: z17.string().min(1, "Procedure ID is required"),
|
|
6545
|
+
eventLocation: clinicLocationSchema,
|
|
6546
|
+
eventTime: calendarEventTimeSchema,
|
|
6547
|
+
description: z17.string().optional()
|
|
6548
|
+
}).refine(
|
|
6549
|
+
(data) => {
|
|
6550
|
+
return true;
|
|
6551
|
+
},
|
|
6552
|
+
{
|
|
6553
|
+
message: "Invalid appointment parameters"
|
|
6554
|
+
}
|
|
6555
|
+
);
|
|
6556
|
+
var updateAppointmentSchema = z17.object({
|
|
6557
|
+
appointmentId: z17.string().min(1, "Appointment ID is required"),
|
|
6558
|
+
clinicId: z17.string().min(1, "Clinic ID is required"),
|
|
6559
|
+
doctorId: z17.string().min(1, "Doctor ID is required"),
|
|
6560
|
+
patientId: z17.string().min(1, "Patient ID is required"),
|
|
6561
|
+
eventTime: calendarEventTimeSchema.optional(),
|
|
6562
|
+
description: z17.string().optional(),
|
|
6563
|
+
status: z17.nativeEnum(CalendarEventStatus).optional()
|
|
6564
|
+
});
|
|
6565
|
+
var createCalendarEventSchema = z17.object({
|
|
6566
|
+
id: z17.string(),
|
|
6567
|
+
clinicBranchId: z17.string().nullable().optional(),
|
|
6568
|
+
clinicBranchInfo: z17.any().nullable().optional(),
|
|
6569
|
+
practitionerProfileId: z17.string().nullable().optional(),
|
|
6570
|
+
practitionerProfileInfo: practitionerProfileInfoSchema.nullable().optional(),
|
|
6571
|
+
patientProfileId: z17.string().nullable().optional(),
|
|
6572
|
+
patientProfileInfo: patientProfileInfoSchema.nullable().optional(),
|
|
6573
|
+
procedureId: z17.string().nullable().optional(),
|
|
6574
|
+
appointmentId: z17.string().nullable().optional(),
|
|
6575
|
+
syncedCalendarEventId: z17.array(syncedCalendarEventSchema).nullable().optional(),
|
|
6576
|
+
eventName: z17.string().min(1, "Event name is required"),
|
|
6577
|
+
eventLocation: clinicLocationSchema.optional(),
|
|
6578
|
+
eventTime: calendarEventTimeSchema,
|
|
6579
|
+
description: z17.string().optional(),
|
|
6580
|
+
status: z17.nativeEnum(CalendarEventStatus),
|
|
6581
|
+
syncStatus: z17.nativeEnum(CalendarSyncStatus),
|
|
6582
|
+
eventType: z17.nativeEnum(CalendarEventType),
|
|
6583
|
+
createdAt: z17.any(),
|
|
6584
|
+
// FieldValue for server timestamp
|
|
6585
|
+
updatedAt: z17.any()
|
|
6586
|
+
// FieldValue for server timestamp
|
|
6587
|
+
});
|
|
6588
|
+
var updateCalendarEventSchema = z17.object({
|
|
6589
|
+
syncedCalendarEventId: z17.array(syncedCalendarEventSchema).nullable().optional(),
|
|
6590
|
+
appointmentId: z17.string().nullable().optional(),
|
|
6591
|
+
eventName: z17.string().optional(),
|
|
6592
|
+
eventTime: calendarEventTimeSchema.optional(),
|
|
6593
|
+
description: z17.string().optional(),
|
|
6594
|
+
status: z17.nativeEnum(CalendarEventStatus).optional(),
|
|
6595
|
+
syncStatus: z17.nativeEnum(CalendarSyncStatus).optional(),
|
|
6596
|
+
eventType: z17.nativeEnum(CalendarEventType).optional(),
|
|
6597
|
+
updatedAt: z17.any()
|
|
6598
|
+
// FieldValue for server timestamp
|
|
6599
|
+
});
|
|
6600
|
+
var calendarEventSchema = z17.object({
|
|
6601
|
+
id: z17.string(),
|
|
6602
|
+
clinicBranchId: z17.string().nullable().optional(),
|
|
6603
|
+
clinicBranchInfo: z17.any().nullable().optional(),
|
|
6604
|
+
// Will be replaced with proper clinic info schema
|
|
6605
|
+
practitionerProfileId: z17.string().nullable().optional(),
|
|
6606
|
+
practitionerProfileInfo: practitionerProfileInfoSchema.nullable().optional(),
|
|
6607
|
+
patientProfileId: z17.string().nullable().optional(),
|
|
6608
|
+
patientProfileInfo: patientProfileInfoSchema.nullable().optional(),
|
|
6609
|
+
procedureId: z17.string().nullable().optional(),
|
|
6610
|
+
procedureInfo: procedureInfoSchema.nullable().optional(),
|
|
6611
|
+
procedureCategorization: procedureCategorizationSchema.nullable().optional(),
|
|
6612
|
+
appointmentId: z17.string().nullable().optional(),
|
|
6613
|
+
syncedCalendarEventId: z17.array(syncedCalendarEventSchema).nullable().optional(),
|
|
6614
|
+
eventName: z17.string(),
|
|
6615
|
+
eventLocation: clinicLocationSchema.optional(),
|
|
6616
|
+
eventTime: calendarEventTimeSchema,
|
|
6617
|
+
description: z17.string().optional(),
|
|
6618
|
+
status: z17.nativeEnum(CalendarEventStatus),
|
|
6619
|
+
syncStatus: z17.nativeEnum(CalendarSyncStatus),
|
|
6620
|
+
eventType: z17.nativeEnum(CalendarEventType),
|
|
6621
|
+
createdAt: z17.instanceof(Date).or(z17.instanceof(Timestamp17)),
|
|
6622
|
+
updatedAt: z17.instanceof(Date).or(z17.instanceof(Timestamp17))
|
|
6623
|
+
});
|
|
6624
|
+
|
|
6625
|
+
// src/services/calendar/utils/clinic.utils.ts
|
|
6626
|
+
import {
|
|
6627
|
+
collection as collection13,
|
|
6628
|
+
doc as doc15,
|
|
6629
|
+
getDoc as getDoc18,
|
|
6630
|
+
getDocs as getDocs12,
|
|
6631
|
+
setDoc as setDoc15,
|
|
6632
|
+
updateDoc as updateDoc16,
|
|
6633
|
+
deleteDoc as deleteDoc8,
|
|
6634
|
+
query as query12,
|
|
6635
|
+
where as where12,
|
|
6636
|
+
orderBy as orderBy4,
|
|
6637
|
+
Timestamp as Timestamp18,
|
|
6638
|
+
serverTimestamp as serverTimestamp14
|
|
6639
|
+
} from "firebase/firestore";
|
|
6640
|
+
|
|
6641
|
+
// src/services/calendar/utils/docs.utils.ts
|
|
6642
|
+
import { doc as doc14 } from "firebase/firestore";
|
|
6643
|
+
function getPractitionerCalendarEventDocRef(db, practitionerId, eventId) {
|
|
6644
|
+
return doc14(
|
|
6645
|
+
db,
|
|
6646
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${CALENDAR_COLLECTION}/${eventId}`
|
|
6647
|
+
);
|
|
6648
|
+
}
|
|
6649
|
+
function getPatientCalendarEventDocRef(db, patientId, eventId) {
|
|
6650
|
+
return doc14(
|
|
6651
|
+
db,
|
|
6652
|
+
`${PATIENTS_COLLECTION}/${patientId}/${CALENDAR_COLLECTION}/${eventId}`
|
|
6653
|
+
);
|
|
6654
|
+
}
|
|
6655
|
+
function getClinicCalendarEventDocRef(db, clinicId, eventId) {
|
|
6656
|
+
return doc14(
|
|
6657
|
+
db,
|
|
6658
|
+
`${CLINICS_COLLECTION}/${clinicId}/${CALENDAR_COLLECTION}/${eventId}`
|
|
6659
|
+
);
|
|
6660
|
+
}
|
|
6661
|
+
function getPractitionerSyncedCalendarDocRef(db, practitionerId, syncedCalendarId) {
|
|
6662
|
+
return doc14(
|
|
6663
|
+
db,
|
|
6664
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/syncedCalendars/${syncedCalendarId}`
|
|
6665
|
+
);
|
|
6666
|
+
}
|
|
6667
|
+
function getPatientSyncedCalendarDocRef(db, patientId, syncedCalendarId) {
|
|
6668
|
+
return doc14(
|
|
6669
|
+
db,
|
|
6670
|
+
`${PATIENTS_COLLECTION}/${patientId}/syncedCalendars/${syncedCalendarId}`
|
|
6671
|
+
);
|
|
6672
|
+
}
|
|
6673
|
+
function getClinicSyncedCalendarDocRef(db, clinicId, syncedCalendarId) {
|
|
6674
|
+
return doc14(
|
|
6675
|
+
db,
|
|
6676
|
+
`${CLINICS_COLLECTION}/${clinicId}/syncedCalendars/${syncedCalendarId}`
|
|
6677
|
+
);
|
|
6678
|
+
}
|
|
6679
|
+
|
|
6680
|
+
// src/services/calendar/utils/clinic.utils.ts
|
|
6681
|
+
async function createClinicCalendarEventUtil(db, clinicId, eventData, generateId2) {
|
|
6682
|
+
const eventId = generateId2();
|
|
6683
|
+
const eventRef = getClinicCalendarEventDocRef(db, clinicId, eventId);
|
|
6684
|
+
const newEvent = {
|
|
6685
|
+
id: eventId,
|
|
6686
|
+
...eventData,
|
|
6687
|
+
createdAt: serverTimestamp14(),
|
|
6688
|
+
updatedAt: serverTimestamp14()
|
|
6689
|
+
};
|
|
6690
|
+
await setDoc15(eventRef, newEvent);
|
|
6691
|
+
return {
|
|
6692
|
+
...newEvent,
|
|
6693
|
+
createdAt: Timestamp18.now(),
|
|
6694
|
+
updatedAt: Timestamp18.now()
|
|
6695
|
+
};
|
|
6696
|
+
}
|
|
6697
|
+
async function updateClinicCalendarEventUtil(db, clinicId, eventId, updateData) {
|
|
6698
|
+
const eventRef = getClinicCalendarEventDocRef(db, clinicId, eventId);
|
|
6699
|
+
const updates = {
|
|
6700
|
+
...updateData,
|
|
6701
|
+
updatedAt: serverTimestamp14()
|
|
6702
|
+
};
|
|
6703
|
+
await updateDoc16(eventRef, updates);
|
|
6704
|
+
const updatedDoc = await getDoc18(eventRef);
|
|
6705
|
+
if (!updatedDoc.exists()) {
|
|
6706
|
+
throw new Error("Event not found after update");
|
|
6707
|
+
}
|
|
6708
|
+
return updatedDoc.data();
|
|
6709
|
+
}
|
|
6710
|
+
async function checkAutoConfirmAppointmentsUtil(db, clinicId) {
|
|
6711
|
+
const clinicDoc = await getDoc18(doc15(db, `clinics/${clinicId}`));
|
|
6712
|
+
if (!clinicDoc.exists()) {
|
|
6713
|
+
throw new Error(`Clinic with ID ${clinicId} not found`);
|
|
6714
|
+
}
|
|
6715
|
+
const clinicData = clinicDoc.data();
|
|
6716
|
+
const clinicGroupId = clinicData.clinicGroupId;
|
|
6717
|
+
if (!clinicGroupId) {
|
|
6718
|
+
return false;
|
|
6719
|
+
}
|
|
6720
|
+
const clinicGroupDoc = await getDoc18(
|
|
6721
|
+
doc15(db, `${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}`)
|
|
6722
|
+
);
|
|
6723
|
+
if (!clinicGroupDoc.exists()) {
|
|
6724
|
+
return false;
|
|
6725
|
+
}
|
|
6726
|
+
const clinicGroupData = clinicGroupDoc.data();
|
|
6727
|
+
return !!clinicGroupData.autoConfirmAppointments;
|
|
6728
|
+
}
|
|
6729
|
+
|
|
6730
|
+
// src/services/calendar/utils/patient.utils.ts
|
|
6731
|
+
import {
|
|
6732
|
+
collection as collection14,
|
|
6733
|
+
getDoc as getDoc19,
|
|
6734
|
+
getDocs as getDocs13,
|
|
6735
|
+
setDoc as setDoc16,
|
|
6736
|
+
updateDoc as updateDoc17,
|
|
6737
|
+
deleteDoc as deleteDoc9,
|
|
6738
|
+
query as query13,
|
|
6739
|
+
where as where13,
|
|
6740
|
+
orderBy as orderBy5,
|
|
6741
|
+
Timestamp as Timestamp19,
|
|
6742
|
+
serverTimestamp as serverTimestamp15
|
|
6743
|
+
} from "firebase/firestore";
|
|
6744
|
+
async function createPatientCalendarEventUtil(db, patientId, eventData, generateId2) {
|
|
6745
|
+
const eventId = generateId2();
|
|
6746
|
+
const eventRef = getPatientCalendarEventDocRef(db, patientId, eventId);
|
|
6747
|
+
const newEvent = {
|
|
6748
|
+
id: eventId,
|
|
6749
|
+
...eventData,
|
|
6750
|
+
createdAt: serverTimestamp15(),
|
|
6751
|
+
updatedAt: serverTimestamp15()
|
|
6752
|
+
};
|
|
6753
|
+
await setDoc16(eventRef, newEvent);
|
|
6754
|
+
return {
|
|
6755
|
+
...newEvent,
|
|
6756
|
+
createdAt: Timestamp19.now(),
|
|
6757
|
+
updatedAt: Timestamp19.now()
|
|
6758
|
+
};
|
|
6759
|
+
}
|
|
6760
|
+
async function updatePatientCalendarEventUtil(db, patientId, eventId, updateData) {
|
|
6761
|
+
const eventRef = getPatientCalendarEventDocRef(db, patientId, eventId);
|
|
6762
|
+
const updates = {
|
|
6763
|
+
...updateData,
|
|
6764
|
+
updatedAt: serverTimestamp15()
|
|
6765
|
+
};
|
|
6766
|
+
await updateDoc17(eventRef, updates);
|
|
6767
|
+
const updatedDoc = await getDoc19(eventRef);
|
|
6768
|
+
if (!updatedDoc.exists()) {
|
|
6769
|
+
throw new Error("Event not found after update");
|
|
6770
|
+
}
|
|
6771
|
+
return updatedDoc.data();
|
|
6772
|
+
}
|
|
6773
|
+
|
|
6774
|
+
// src/services/calendar/utils/practitioner.utils.ts
|
|
6775
|
+
import {
|
|
6776
|
+
collection as collection15,
|
|
6777
|
+
getDoc as getDoc20,
|
|
6778
|
+
getDocs as getDocs14,
|
|
6779
|
+
setDoc as setDoc17,
|
|
6780
|
+
updateDoc as updateDoc18,
|
|
6781
|
+
deleteDoc as deleteDoc10,
|
|
6782
|
+
query as query14,
|
|
6783
|
+
where as where14,
|
|
6784
|
+
orderBy as orderBy6,
|
|
6785
|
+
Timestamp as Timestamp20,
|
|
6786
|
+
serverTimestamp as serverTimestamp16
|
|
6787
|
+
} from "firebase/firestore";
|
|
6788
|
+
async function createPractitionerCalendarEventUtil(db, practitionerId, eventData, generateId2) {
|
|
6789
|
+
const eventId = generateId2();
|
|
6790
|
+
const eventRef = getPractitionerCalendarEventDocRef(
|
|
6791
|
+
db,
|
|
6792
|
+
practitionerId,
|
|
6793
|
+
eventId
|
|
6794
|
+
);
|
|
6795
|
+
const newEvent = {
|
|
6796
|
+
id: eventId,
|
|
6797
|
+
...eventData,
|
|
6798
|
+
createdAt: serverTimestamp16(),
|
|
6799
|
+
updatedAt: serverTimestamp16()
|
|
6800
|
+
};
|
|
6801
|
+
await setDoc17(eventRef, newEvent);
|
|
6802
|
+
return {
|
|
6803
|
+
...newEvent,
|
|
6804
|
+
createdAt: Timestamp20.now(),
|
|
6805
|
+
updatedAt: Timestamp20.now()
|
|
6806
|
+
};
|
|
6807
|
+
}
|
|
6808
|
+
async function updatePractitionerCalendarEventUtil(db, practitionerId, eventId, updateData) {
|
|
6809
|
+
const eventRef = getPractitionerCalendarEventDocRef(
|
|
6810
|
+
db,
|
|
6811
|
+
practitionerId,
|
|
6812
|
+
eventId
|
|
6813
|
+
);
|
|
6814
|
+
const updates = {
|
|
6815
|
+
...updateData,
|
|
6816
|
+
updatedAt: serverTimestamp16()
|
|
6817
|
+
};
|
|
6818
|
+
await updateDoc18(eventRef, updates);
|
|
6819
|
+
const updatedDoc = await getDoc20(eventRef);
|
|
6820
|
+
if (!updatedDoc.exists()) {
|
|
6821
|
+
throw new Error("Event not found after update");
|
|
6822
|
+
}
|
|
6823
|
+
return updatedDoc.data();
|
|
6824
|
+
}
|
|
6825
|
+
|
|
6826
|
+
// src/services/calendar/utils/appointment.utils.ts
|
|
6827
|
+
async function createAppointmentUtil(db, clinicId, practitionerId, patientId, eventData, generateId2) {
|
|
6828
|
+
const eventId = generateId2();
|
|
6829
|
+
const autoConfirm = await checkAutoConfirmAppointmentsUtil(db, clinicId);
|
|
6830
|
+
const initialStatus = autoConfirm ? "confirmed" /* CONFIRMED */ : "pending" /* PENDING */;
|
|
6831
|
+
const appointmentData = {
|
|
6832
|
+
...eventData,
|
|
6833
|
+
clinicBranchId: clinicId,
|
|
6834
|
+
practitionerProfileId: practitionerId,
|
|
6835
|
+
patientProfileId: patientId,
|
|
6836
|
+
eventType: "appointment" /* APPOINTMENT */,
|
|
6837
|
+
status: eventData.status || initialStatus
|
|
6838
|
+
};
|
|
6839
|
+
const clinicPromise = createClinicCalendarEventUtil(
|
|
6840
|
+
db,
|
|
6841
|
+
clinicId,
|
|
6842
|
+
appointmentData,
|
|
6843
|
+
() => eventId
|
|
6844
|
+
// Use the same ID for all calendars
|
|
6845
|
+
);
|
|
6846
|
+
const practitionerPromise = createPractitionerCalendarEventUtil(
|
|
6847
|
+
db,
|
|
6848
|
+
practitionerId,
|
|
6849
|
+
appointmentData,
|
|
6850
|
+
() => eventId
|
|
6851
|
+
// Use the same ID for all calendars
|
|
6852
|
+
);
|
|
6853
|
+
const patientPromise = createPatientCalendarEventUtil(
|
|
6854
|
+
db,
|
|
6855
|
+
patientId,
|
|
6856
|
+
appointmentData,
|
|
6857
|
+
() => eventId
|
|
6858
|
+
// Use the same ID for all calendars
|
|
6859
|
+
);
|
|
6860
|
+
const [clinicEvent] = await Promise.all([
|
|
6861
|
+
clinicPromise,
|
|
6862
|
+
practitionerPromise,
|
|
6863
|
+
patientPromise
|
|
6864
|
+
]);
|
|
6865
|
+
return clinicEvent;
|
|
6866
|
+
}
|
|
6867
|
+
async function updateAppointmentUtil(db, clinicId, practitionerId, patientId, eventId, updateData) {
|
|
6868
|
+
const clinicPromise = updateClinicCalendarEventUtil(
|
|
6869
|
+
db,
|
|
6870
|
+
clinicId,
|
|
6871
|
+
eventId,
|
|
6872
|
+
updateData
|
|
6873
|
+
);
|
|
6874
|
+
const practitionerPromise = updatePractitionerCalendarEventUtil(
|
|
6875
|
+
db,
|
|
6876
|
+
practitionerId,
|
|
6877
|
+
eventId,
|
|
6878
|
+
updateData
|
|
6879
|
+
);
|
|
6880
|
+
const patientPromise = updatePatientCalendarEventUtil(
|
|
6881
|
+
db,
|
|
6882
|
+
patientId,
|
|
6883
|
+
eventId,
|
|
6884
|
+
updateData
|
|
6885
|
+
);
|
|
6886
|
+
const [clinicEvent] = await Promise.all([
|
|
6887
|
+
clinicPromise,
|
|
6888
|
+
practitionerPromise,
|
|
6889
|
+
patientPromise
|
|
6890
|
+
]);
|
|
6891
|
+
return clinicEvent;
|
|
6892
|
+
}
|
|
6893
|
+
|
|
6894
|
+
// src/services/calendar/utils/synced-calendar.utils.ts
|
|
6895
|
+
import {
|
|
6896
|
+
collection as collection16,
|
|
6897
|
+
getDoc as getDoc21,
|
|
6898
|
+
getDocs as getDocs15,
|
|
6899
|
+
setDoc as setDoc18,
|
|
6900
|
+
updateDoc as updateDoc19,
|
|
6901
|
+
deleteDoc as deleteDoc11,
|
|
6902
|
+
query as query15,
|
|
6903
|
+
orderBy as orderBy7,
|
|
6904
|
+
Timestamp as Timestamp21,
|
|
6905
|
+
serverTimestamp as serverTimestamp17
|
|
6906
|
+
} from "firebase/firestore";
|
|
6907
|
+
async function createPractitionerSyncedCalendarUtil(db, practitionerId, calendarData, generateId2) {
|
|
6908
|
+
const calendarId = generateId2();
|
|
6909
|
+
const calendarRef = getPractitionerSyncedCalendarDocRef(
|
|
6910
|
+
db,
|
|
6911
|
+
practitionerId,
|
|
6912
|
+
calendarId
|
|
6913
|
+
);
|
|
6914
|
+
const newCalendar = {
|
|
6915
|
+
id: calendarId,
|
|
6916
|
+
...calendarData,
|
|
6917
|
+
createdAt: serverTimestamp17(),
|
|
6918
|
+
updatedAt: serverTimestamp17()
|
|
6919
|
+
};
|
|
6920
|
+
await setDoc18(calendarRef, newCalendar);
|
|
6921
|
+
return {
|
|
6922
|
+
...newCalendar,
|
|
6923
|
+
createdAt: Timestamp21.now(),
|
|
6924
|
+
updatedAt: Timestamp21.now()
|
|
6925
|
+
};
|
|
6926
|
+
}
|
|
6927
|
+
async function createPatientSyncedCalendarUtil(db, patientId, calendarData, generateId2) {
|
|
6928
|
+
const calendarId = generateId2();
|
|
6929
|
+
const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
|
|
6930
|
+
const newCalendar = {
|
|
6931
|
+
id: calendarId,
|
|
6932
|
+
...calendarData,
|
|
6933
|
+
createdAt: serverTimestamp17(),
|
|
6934
|
+
updatedAt: serverTimestamp17()
|
|
6935
|
+
};
|
|
6936
|
+
await setDoc18(calendarRef, newCalendar);
|
|
6937
|
+
return {
|
|
6938
|
+
...newCalendar,
|
|
6939
|
+
createdAt: Timestamp21.now(),
|
|
6940
|
+
updatedAt: Timestamp21.now()
|
|
6941
|
+
};
|
|
6942
|
+
}
|
|
6943
|
+
async function createClinicSyncedCalendarUtil(db, clinicId, calendarData, generateId2) {
|
|
6944
|
+
const calendarId = generateId2();
|
|
6945
|
+
const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
|
|
6946
|
+
const newCalendar = {
|
|
6947
|
+
id: calendarId,
|
|
6948
|
+
...calendarData,
|
|
6949
|
+
createdAt: serverTimestamp17(),
|
|
6950
|
+
updatedAt: serverTimestamp17()
|
|
6951
|
+
};
|
|
6952
|
+
await setDoc18(calendarRef, newCalendar);
|
|
6953
|
+
return {
|
|
6954
|
+
...newCalendar,
|
|
6955
|
+
createdAt: Timestamp21.now(),
|
|
6956
|
+
updatedAt: Timestamp21.now()
|
|
6957
|
+
};
|
|
6958
|
+
}
|
|
6959
|
+
async function getPractitionerSyncedCalendarUtil(db, practitionerId, calendarId) {
|
|
6960
|
+
const calendarRef = getPractitionerSyncedCalendarDocRef(
|
|
6961
|
+
db,
|
|
6962
|
+
practitionerId,
|
|
6963
|
+
calendarId
|
|
6964
|
+
);
|
|
6965
|
+
const calendarDoc = await getDoc21(calendarRef);
|
|
6966
|
+
if (!calendarDoc.exists()) {
|
|
6967
|
+
return null;
|
|
6968
|
+
}
|
|
6969
|
+
return calendarDoc.data();
|
|
6970
|
+
}
|
|
6971
|
+
async function getPractitionerSyncedCalendarsUtil(db, practitionerId) {
|
|
6972
|
+
const calendarsRef = collection16(
|
|
6973
|
+
db,
|
|
6974
|
+
`practitioners/${practitionerId}/${SYNCED_CALENDARS_COLLECTION}`
|
|
6975
|
+
);
|
|
6976
|
+
const q = query15(calendarsRef, orderBy7("createdAt", "desc"));
|
|
6977
|
+
const querySnapshot = await getDocs15(q);
|
|
6978
|
+
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
6979
|
+
}
|
|
6980
|
+
async function getPatientSyncedCalendarUtil(db, patientId, calendarId) {
|
|
6981
|
+
const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
|
|
6982
|
+
const calendarDoc = await getDoc21(calendarRef);
|
|
6983
|
+
if (!calendarDoc.exists()) {
|
|
6984
|
+
return null;
|
|
6985
|
+
}
|
|
6986
|
+
return calendarDoc.data();
|
|
6987
|
+
}
|
|
6988
|
+
async function getPatientSyncedCalendarsUtil(db, patientId) {
|
|
6989
|
+
const calendarsRef = collection16(
|
|
6990
|
+
db,
|
|
6991
|
+
`patients/${patientId}/${SYNCED_CALENDARS_COLLECTION}`
|
|
6992
|
+
);
|
|
6993
|
+
const q = query15(calendarsRef, orderBy7("createdAt", "desc"));
|
|
6994
|
+
const querySnapshot = await getDocs15(q);
|
|
6995
|
+
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
6996
|
+
}
|
|
6997
|
+
async function getClinicSyncedCalendarUtil(db, clinicId, calendarId) {
|
|
6998
|
+
const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
|
|
6999
|
+
const calendarDoc = await getDoc21(calendarRef);
|
|
7000
|
+
if (!calendarDoc.exists()) {
|
|
7001
|
+
return null;
|
|
7002
|
+
}
|
|
7003
|
+
return calendarDoc.data();
|
|
7004
|
+
}
|
|
7005
|
+
async function getClinicSyncedCalendarsUtil(db, clinicId) {
|
|
7006
|
+
const calendarsRef = collection16(
|
|
7007
|
+
db,
|
|
7008
|
+
`clinics/${clinicId}/${SYNCED_CALENDARS_COLLECTION}`
|
|
7009
|
+
);
|
|
7010
|
+
const q = query15(calendarsRef, orderBy7("createdAt", "desc"));
|
|
7011
|
+
const querySnapshot = await getDocs15(q);
|
|
7012
|
+
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
7013
|
+
}
|
|
7014
|
+
async function updatePractitionerSyncedCalendarUtil(db, practitionerId, calendarId, updateData) {
|
|
7015
|
+
const calendarRef = getPractitionerSyncedCalendarDocRef(
|
|
7016
|
+
db,
|
|
7017
|
+
practitionerId,
|
|
7018
|
+
calendarId
|
|
7019
|
+
);
|
|
7020
|
+
const updates = {
|
|
7021
|
+
...updateData,
|
|
7022
|
+
updatedAt: serverTimestamp17()
|
|
7023
|
+
};
|
|
7024
|
+
await updateDoc19(calendarRef, updates);
|
|
7025
|
+
const updatedDoc = await getDoc21(calendarRef);
|
|
7026
|
+
if (!updatedDoc.exists()) {
|
|
7027
|
+
throw new Error("Synced calendar not found after update");
|
|
7028
|
+
}
|
|
7029
|
+
return updatedDoc.data();
|
|
7030
|
+
}
|
|
7031
|
+
async function updatePatientSyncedCalendarUtil(db, patientId, calendarId, updateData) {
|
|
7032
|
+
const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
|
|
7033
|
+
const updates = {
|
|
7034
|
+
...updateData,
|
|
7035
|
+
updatedAt: serverTimestamp17()
|
|
7036
|
+
};
|
|
7037
|
+
await updateDoc19(calendarRef, updates);
|
|
7038
|
+
const updatedDoc = await getDoc21(calendarRef);
|
|
7039
|
+
if (!updatedDoc.exists()) {
|
|
7040
|
+
throw new Error("Synced calendar not found after update");
|
|
7041
|
+
}
|
|
7042
|
+
return updatedDoc.data();
|
|
7043
|
+
}
|
|
7044
|
+
async function updateClinicSyncedCalendarUtil(db, clinicId, calendarId, updateData) {
|
|
7045
|
+
const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
|
|
7046
|
+
const updates = {
|
|
7047
|
+
...updateData,
|
|
7048
|
+
updatedAt: serverTimestamp17()
|
|
7049
|
+
};
|
|
7050
|
+
await updateDoc19(calendarRef, updates);
|
|
7051
|
+
const updatedDoc = await getDoc21(calendarRef);
|
|
7052
|
+
if (!updatedDoc.exists()) {
|
|
7053
|
+
throw new Error("Synced calendar not found after update");
|
|
7054
|
+
}
|
|
7055
|
+
return updatedDoc.data();
|
|
7056
|
+
}
|
|
7057
|
+
async function deletePractitionerSyncedCalendarUtil(db, practitionerId, calendarId) {
|
|
7058
|
+
const calendarRef = getPractitionerSyncedCalendarDocRef(
|
|
7059
|
+
db,
|
|
7060
|
+
practitionerId,
|
|
7061
|
+
calendarId
|
|
7062
|
+
);
|
|
7063
|
+
await deleteDoc11(calendarRef);
|
|
7064
|
+
}
|
|
7065
|
+
async function deletePatientSyncedCalendarUtil(db, patientId, calendarId) {
|
|
7066
|
+
const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
|
|
7067
|
+
await deleteDoc11(calendarRef);
|
|
7068
|
+
}
|
|
7069
|
+
async function deleteClinicSyncedCalendarUtil(db, clinicId, calendarId) {
|
|
7070
|
+
const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
|
|
7071
|
+
await deleteDoc11(calendarRef);
|
|
7072
|
+
}
|
|
7073
|
+
async function updateLastSyncedTimestampUtil(db, entityType, entityId, calendarId) {
|
|
7074
|
+
const updateData = {
|
|
7075
|
+
lastSyncedAt: Timestamp21.now()
|
|
7076
|
+
};
|
|
7077
|
+
switch (entityType) {
|
|
7078
|
+
case "practitioner":
|
|
7079
|
+
return updatePractitionerSyncedCalendarUtil(
|
|
7080
|
+
db,
|
|
7081
|
+
entityId,
|
|
7082
|
+
calendarId,
|
|
7083
|
+
updateData
|
|
7084
|
+
);
|
|
7085
|
+
case "patient":
|
|
7086
|
+
return updatePatientSyncedCalendarUtil(
|
|
7087
|
+
db,
|
|
7088
|
+
entityId,
|
|
7089
|
+
calendarId,
|
|
7090
|
+
updateData
|
|
7091
|
+
);
|
|
7092
|
+
case "clinic":
|
|
7093
|
+
return updateClinicSyncedCalendarUtil(
|
|
7094
|
+
db,
|
|
7095
|
+
entityId,
|
|
7096
|
+
calendarId,
|
|
7097
|
+
updateData
|
|
7098
|
+
);
|
|
7099
|
+
default:
|
|
7100
|
+
throw new Error(`Invalid entity type: ${entityType}`);
|
|
7101
|
+
}
|
|
7102
|
+
}
|
|
7103
|
+
|
|
7104
|
+
// src/services/calendar/utils/google-calendar.utils.ts
|
|
7105
|
+
import { Timestamp as Timestamp22 } from "firebase/firestore";
|
|
7106
|
+
var GOOGLE_CALENDAR_API_URL = "https://www.googleapis.com/calendar/v3";
|
|
7107
|
+
var GOOGLE_OAUTH_URL = "https://oauth2.googleapis.com/token";
|
|
7108
|
+
var CLIENT_ID = "your-client-id";
|
|
7109
|
+
var CLIENT_SECRET = "your-client-secret";
|
|
7110
|
+
var REDIRECT_URI = "your-redirect-uri";
|
|
7111
|
+
async function makeRequest(method, url, headers, data, params) {
|
|
7112
|
+
const queryParams = params ? "?" + Object.entries(params).map(
|
|
7113
|
+
([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
|
|
7114
|
+
).join("&") : "";
|
|
7115
|
+
const finalUrl = url + queryParams;
|
|
7116
|
+
const options = {
|
|
7117
|
+
method,
|
|
7118
|
+
headers,
|
|
7119
|
+
body: data ? JSON.stringify(data) : void 0
|
|
7120
|
+
};
|
|
7121
|
+
const response = await fetch(finalUrl, options);
|
|
7122
|
+
if (!response.ok) {
|
|
7123
|
+
const error = new Error(
|
|
7124
|
+
`Request failed with status ${response.status}`
|
|
7125
|
+
);
|
|
7126
|
+
error.response = response;
|
|
7127
|
+
throw error;
|
|
7128
|
+
}
|
|
7129
|
+
return response.json();
|
|
7130
|
+
}
|
|
7131
|
+
async function authenticateWithGoogleCalendarUtil(authCode) {
|
|
7132
|
+
try {
|
|
7133
|
+
const data = {
|
|
7134
|
+
code: authCode,
|
|
7135
|
+
client_id: CLIENT_ID,
|
|
7136
|
+
client_secret: CLIENT_SECRET,
|
|
7137
|
+
redirect_uri: REDIRECT_URI,
|
|
7138
|
+
grant_type: "authorization_code"
|
|
7139
|
+
};
|
|
7140
|
+
const response = await makeRequest(
|
|
7141
|
+
"post",
|
|
7142
|
+
GOOGLE_OAUTH_URL,
|
|
7143
|
+
{ "Content-Type": "application/json" },
|
|
7144
|
+
data
|
|
7145
|
+
);
|
|
7146
|
+
return {
|
|
7147
|
+
accessToken: response.access_token,
|
|
7148
|
+
refreshToken: response.refresh_token,
|
|
7149
|
+
expiresIn: response.expires_in
|
|
7150
|
+
};
|
|
7151
|
+
} catch (error) {
|
|
7152
|
+
const apiError = error;
|
|
7153
|
+
console.error(
|
|
7154
|
+
"Error authenticating with Google Calendar:",
|
|
7155
|
+
apiError.message || "Unknown error"
|
|
7156
|
+
);
|
|
7157
|
+
throw new Error(
|
|
7158
|
+
`Failed to authenticate with Google Calendar: ${apiError.message || "Unknown error"}`
|
|
7159
|
+
);
|
|
7160
|
+
}
|
|
7161
|
+
}
|
|
7162
|
+
async function refreshGoogleCalendarTokenUtil(refreshToken) {
|
|
7163
|
+
try {
|
|
7164
|
+
const data = {
|
|
7165
|
+
refresh_token: refreshToken,
|
|
7166
|
+
client_id: CLIENT_ID,
|
|
7167
|
+
client_secret: CLIENT_SECRET,
|
|
7168
|
+
grant_type: "refresh_token"
|
|
7169
|
+
};
|
|
7170
|
+
const response = await makeRequest(
|
|
7171
|
+
"post",
|
|
7172
|
+
GOOGLE_OAUTH_URL,
|
|
7173
|
+
{ "Content-Type": "application/json" },
|
|
7174
|
+
data
|
|
7175
|
+
);
|
|
7176
|
+
return {
|
|
7177
|
+
accessToken: response.access_token,
|
|
7178
|
+
expiresIn: response.expires_in
|
|
7179
|
+
};
|
|
7180
|
+
} catch (error) {
|
|
7181
|
+
const apiError = error;
|
|
7182
|
+
console.error(
|
|
7183
|
+
"Error refreshing Google Calendar token:",
|
|
7184
|
+
apiError.message || "Unknown error"
|
|
7185
|
+
);
|
|
7186
|
+
throw new Error(
|
|
7187
|
+
`Failed to refresh Google Calendar token: ${apiError.message || "Unknown error"}`
|
|
7188
|
+
);
|
|
7189
|
+
}
|
|
7190
|
+
}
|
|
7191
|
+
async function listGoogleCalendarsUtil(accessToken) {
|
|
7192
|
+
try {
|
|
7193
|
+
const response = await makeRequest(
|
|
7194
|
+
"get",
|
|
7195
|
+
`${GOOGLE_CALENDAR_API_URL}/users/me/calendarList`,
|
|
7196
|
+
{ Authorization: `Bearer ${accessToken}` }
|
|
7197
|
+
);
|
|
7198
|
+
return response.items.map((calendar) => ({
|
|
7199
|
+
id: calendar.id,
|
|
7200
|
+
name: calendar.summary
|
|
7201
|
+
}));
|
|
7202
|
+
} catch (error) {
|
|
7203
|
+
const apiError = error;
|
|
7204
|
+
console.error(
|
|
7205
|
+
"Error listing Google Calendars:",
|
|
7206
|
+
apiError.message || "Unknown error"
|
|
7207
|
+
);
|
|
7208
|
+
throw new Error(
|
|
7209
|
+
`Failed to list Google Calendars: ${apiError.message || "Unknown error"}`
|
|
7210
|
+
);
|
|
7211
|
+
}
|
|
7212
|
+
}
|
|
7213
|
+
async function ensureValidToken(db, entityType, entityId, syncedCalendar) {
|
|
7214
|
+
const expiryTime = syncedCalendar.tokenExpiry.toDate();
|
|
7215
|
+
const now = /* @__PURE__ */ new Date();
|
|
7216
|
+
const fiveMinutesFromNow = new Date(now.getTime() + 5 * 60 * 1e3);
|
|
7217
|
+
if (expiryTime < fiveMinutesFromNow) {
|
|
7218
|
+
const { accessToken, expiresIn } = await refreshGoogleCalendarTokenUtil(
|
|
7219
|
+
syncedCalendar.refreshToken
|
|
7220
|
+
);
|
|
7221
|
+
const tokenExpiry = /* @__PURE__ */ new Date();
|
|
7222
|
+
tokenExpiry.setSeconds(tokenExpiry.getSeconds() + expiresIn);
|
|
7223
|
+
const updateData = {
|
|
7224
|
+
accessToken,
|
|
7225
|
+
tokenExpiry: Timestamp22.fromDate(tokenExpiry)
|
|
7226
|
+
};
|
|
7227
|
+
switch (entityType) {
|
|
7228
|
+
case "practitioner":
|
|
7229
|
+
await updatePractitionerSyncedCalendarUtil(
|
|
7230
|
+
db,
|
|
7231
|
+
entityId,
|
|
7232
|
+
syncedCalendar.id,
|
|
7233
|
+
updateData
|
|
7234
|
+
);
|
|
7235
|
+
break;
|
|
7236
|
+
case "patient":
|
|
7237
|
+
await updatePatientSyncedCalendarUtil(
|
|
7238
|
+
db,
|
|
7239
|
+
entityId,
|
|
7240
|
+
syncedCalendar.id,
|
|
7241
|
+
updateData
|
|
7242
|
+
);
|
|
7243
|
+
break;
|
|
7244
|
+
case "clinic":
|
|
7245
|
+
await updateClinicSyncedCalendarUtil(
|
|
7246
|
+
db,
|
|
7247
|
+
entityId,
|
|
7248
|
+
syncedCalendar.id,
|
|
7249
|
+
updateData
|
|
7250
|
+
);
|
|
7251
|
+
break;
|
|
7252
|
+
}
|
|
7253
|
+
return accessToken;
|
|
7254
|
+
}
|
|
7255
|
+
return syncedCalendar.accessToken;
|
|
7256
|
+
}
|
|
7257
|
+
async function syncEventsToGoogleCalendarUtil(db, entityType, entityId, syncedCalendar, events, existingSyncId) {
|
|
7258
|
+
var _a, _b;
|
|
7259
|
+
try {
|
|
7260
|
+
const { accessToken } = await refreshGoogleCalendarTokenUtil(
|
|
7261
|
+
syncedCalendar.refreshToken
|
|
7262
|
+
);
|
|
7263
|
+
let syncedCount = 0;
|
|
7264
|
+
const errors = [];
|
|
7265
|
+
const eventIds = [];
|
|
7266
|
+
for (const event of events) {
|
|
7267
|
+
try {
|
|
7268
|
+
if (event.syncStatus === "external" /* EXTERNAL */) {
|
|
7269
|
+
continue;
|
|
7270
|
+
}
|
|
7271
|
+
if (entityType === "practitioner" && event.status !== "confirmed" /* CONFIRMED */) {
|
|
7272
|
+
continue;
|
|
7273
|
+
}
|
|
7274
|
+
if (entityType === "patient" && (event.status === "canceled" /* CANCELED */ || event.status === "rejected" /* REJECTED */)) {
|
|
7275
|
+
continue;
|
|
7276
|
+
}
|
|
7277
|
+
if (entityType === "clinic") {
|
|
7278
|
+
continue;
|
|
7279
|
+
}
|
|
7280
|
+
const googleEvent = convertCalendarEventToGoogleEventUtil(event);
|
|
7281
|
+
const headers = {
|
|
7282
|
+
Authorization: `Bearer ${accessToken}`,
|
|
7283
|
+
"Content-Type": "application/json"
|
|
7284
|
+
};
|
|
7285
|
+
let responseId = "";
|
|
7286
|
+
if (existingSyncId) {
|
|
7287
|
+
const response = await makeRequest(
|
|
7288
|
+
"put",
|
|
7289
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${existingSyncId}`,
|
|
7290
|
+
headers,
|
|
7291
|
+
googleEvent
|
|
7292
|
+
);
|
|
7293
|
+
responseId = response.id;
|
|
7294
|
+
} else {
|
|
7295
|
+
const existingSync = (_a = event.syncedCalendarEventId) == null ? void 0 : _a.find(
|
|
7296
|
+
(sync) => sync.syncedCalendarProvider === "google" /* GOOGLE */ && // We should check if this is the same calendar we're syncing with, but that information isn't stored
|
|
7297
|
+
// For now, we'll just use the first Google Calendar sync ID
|
|
7298
|
+
sync.syncedCalendarProvider === syncedCalendar.provider
|
|
7299
|
+
);
|
|
7300
|
+
if (existingSync) {
|
|
7301
|
+
const response = await makeRequest(
|
|
7302
|
+
"put",
|
|
7303
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${existingSync.eventId}`,
|
|
7304
|
+
headers,
|
|
7305
|
+
googleEvent
|
|
7306
|
+
);
|
|
7307
|
+
responseId = response.id;
|
|
7308
|
+
} else {
|
|
7309
|
+
const response = await makeRequest(
|
|
7310
|
+
"post",
|
|
7311
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events`,
|
|
7312
|
+
headers,
|
|
7313
|
+
googleEvent
|
|
7314
|
+
);
|
|
7315
|
+
responseId = response.id;
|
|
7316
|
+
}
|
|
7317
|
+
}
|
|
7318
|
+
if (responseId) {
|
|
7319
|
+
eventIds.push(responseId);
|
|
7320
|
+
syncedCount++;
|
|
7321
|
+
}
|
|
7322
|
+
} catch (error) {
|
|
7323
|
+
const apiError = error;
|
|
7324
|
+
errors.push({
|
|
7325
|
+
eventId: event.id,
|
|
7326
|
+
error: apiError.message || "Unknown error",
|
|
7327
|
+
status: (_b = apiError.response) == null ? void 0 : _b.status
|
|
7328
|
+
});
|
|
7329
|
+
}
|
|
7330
|
+
}
|
|
7331
|
+
await updateLastSyncedTimestampUtil(
|
|
7332
|
+
db,
|
|
7333
|
+
entityType,
|
|
7334
|
+
entityId,
|
|
7335
|
+
syncedCalendar.id
|
|
7336
|
+
);
|
|
7337
|
+
return {
|
|
7338
|
+
success: errors.length === 0,
|
|
7339
|
+
syncedEvents: syncedCount,
|
|
7340
|
+
errors,
|
|
7341
|
+
eventIds
|
|
7342
|
+
};
|
|
7343
|
+
} catch (error) {
|
|
7344
|
+
console.error("Error syncing with Google Calendar:", error);
|
|
7345
|
+
return {
|
|
7346
|
+
success: false,
|
|
7347
|
+
syncedEvents: 0,
|
|
7348
|
+
errors: [{ error: error.message || "Unknown error" }],
|
|
7349
|
+
eventIds: []
|
|
7350
|
+
};
|
|
7351
|
+
}
|
|
7352
|
+
}
|
|
7353
|
+
async function fetchEventsFromGoogleCalendarUtil(db, entityType, entityId, syncedCalendar, startDate, endDate) {
|
|
7354
|
+
try {
|
|
7355
|
+
const accessToken = await ensureValidToken(
|
|
7356
|
+
db,
|
|
7357
|
+
entityType,
|
|
7358
|
+
entityId,
|
|
7359
|
+
syncedCalendar
|
|
7360
|
+
);
|
|
7361
|
+
const timeMin = startDate.toISOString();
|
|
7362
|
+
const timeMax = endDate.toISOString();
|
|
7363
|
+
const response = await makeRequest(
|
|
7364
|
+
"get",
|
|
7365
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events`,
|
|
7366
|
+
{ Authorization: `Bearer ${accessToken}` },
|
|
7367
|
+
void 0,
|
|
7368
|
+
{
|
|
7369
|
+
timeMin,
|
|
7370
|
+
timeMax,
|
|
7371
|
+
singleEvents: "true",
|
|
7372
|
+
orderBy: "startTime"
|
|
7373
|
+
}
|
|
7374
|
+
);
|
|
7375
|
+
await updateLastSyncedTimestampUtil(
|
|
7376
|
+
db,
|
|
7377
|
+
entityType,
|
|
7378
|
+
entityId,
|
|
7379
|
+
syncedCalendar.id
|
|
7380
|
+
);
|
|
7381
|
+
return response.items;
|
|
7382
|
+
} catch (error) {
|
|
7383
|
+
const apiError = error;
|
|
7384
|
+
console.error(
|
|
7385
|
+
"Error fetching events from Google Calendar:",
|
|
7386
|
+
apiError.message || "Unknown error"
|
|
7387
|
+
);
|
|
7388
|
+
throw new Error(
|
|
7389
|
+
`Failed to fetch events from Google Calendar: ${apiError.message || "Unknown error"}`
|
|
7390
|
+
);
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7393
|
+
function convertGoogleEventToCalendarEventUtil(googleEvent, entityId, entityType) {
|
|
7394
|
+
const start = googleEvent.start.dateTime ? new Date(googleEvent.start.dateTime) : new Date(googleEvent.start.date);
|
|
7395
|
+
const end = googleEvent.end.dateTime ? new Date(googleEvent.end.dateTime) : new Date(googleEvent.end.date);
|
|
7396
|
+
const calendarEvent = {
|
|
7397
|
+
eventName: googleEvent.summary || "External Event",
|
|
7398
|
+
eventLocation: googleEvent.location,
|
|
7399
|
+
eventTime: {
|
|
7400
|
+
start: Timestamp22.fromDate(start),
|
|
7401
|
+
end: Timestamp22.fromDate(end)
|
|
7402
|
+
},
|
|
7403
|
+
description: googleEvent.description || "",
|
|
7404
|
+
// External events are always set as CONFIRMED - status updates will happen externally
|
|
7405
|
+
status: "confirmed" /* CONFIRMED */,
|
|
7406
|
+
// All external events are marked as EXTERNAL to indicate they originated outside our system
|
|
7407
|
+
syncStatus: "external" /* EXTERNAL */,
|
|
7408
|
+
// All external events are treated as BLOCKING events
|
|
7409
|
+
eventType: "blocking" /* BLOCKING */,
|
|
7410
|
+
// Store the original Google Calendar event ID
|
|
7411
|
+
syncedCalendarEventId: [
|
|
7412
|
+
{
|
|
7413
|
+
eventId: googleEvent.id,
|
|
7414
|
+
syncedCalendarProvider: "google" /* GOOGLE */,
|
|
7415
|
+
syncedAt: Timestamp22.now()
|
|
7416
|
+
}
|
|
7417
|
+
]
|
|
7418
|
+
};
|
|
7419
|
+
switch (entityType) {
|
|
7420
|
+
case "practitioner":
|
|
7421
|
+
calendarEvent.practitionerProfileId = entityId;
|
|
7422
|
+
break;
|
|
7423
|
+
case "patient":
|
|
7424
|
+
calendarEvent.patientProfileId = entityId;
|
|
7425
|
+
break;
|
|
7426
|
+
case "clinic":
|
|
7427
|
+
calendarEvent.clinicBranchId = entityId;
|
|
7428
|
+
break;
|
|
7429
|
+
}
|
|
7430
|
+
return calendarEvent;
|
|
7431
|
+
}
|
|
7432
|
+
function convertCalendarEventToGoogleEventUtil(calendarEvent) {
|
|
7433
|
+
const googleEvent = {
|
|
7434
|
+
summary: calendarEvent.eventName,
|
|
7435
|
+
location: calendarEvent.eventLocation,
|
|
7436
|
+
description: calendarEvent.description,
|
|
7437
|
+
start: {
|
|
7438
|
+
dateTime: calendarEvent.eventTime.start.toDate().toISOString(),
|
|
7439
|
+
timeZone: "UTC"
|
|
7440
|
+
},
|
|
7441
|
+
end: {
|
|
7442
|
+
dateTime: calendarEvent.eventTime.end.toDate().toISOString(),
|
|
7443
|
+
timeZone: "UTC"
|
|
7444
|
+
},
|
|
7445
|
+
// Add reminders
|
|
7446
|
+
reminders: {
|
|
7447
|
+
useDefault: false,
|
|
7448
|
+
overrides: [
|
|
7449
|
+
{ method: "email", minutes: 24 * 60 },
|
|
7450
|
+
// 1 day before
|
|
7451
|
+
{ method: "popup", minutes: 30 }
|
|
7452
|
+
// 30 minutes before
|
|
7453
|
+
]
|
|
7454
|
+
}
|
|
7455
|
+
};
|
|
7456
|
+
switch (calendarEvent.status) {
|
|
7457
|
+
case "confirmed" /* CONFIRMED */:
|
|
7458
|
+
googleEvent.status = "confirmed";
|
|
7459
|
+
break;
|
|
7460
|
+
case "canceled" /* CANCELED */:
|
|
7461
|
+
googleEvent.status = "cancelled";
|
|
7462
|
+
break;
|
|
7463
|
+
case "pending" /* PENDING */:
|
|
7464
|
+
googleEvent.status = "tentative";
|
|
7465
|
+
break;
|
|
7466
|
+
default:
|
|
7467
|
+
googleEvent.status = "confirmed";
|
|
7468
|
+
}
|
|
7469
|
+
if (calendarEvent.eventType === "appointment" /* APPOINTMENT */) {
|
|
7470
|
+
googleEvent.attendees = [];
|
|
7471
|
+
if (calendarEvent.practitionerProfileId) {
|
|
7472
|
+
googleEvent.attendees.push({
|
|
7473
|
+
email: "practitioner@example.com",
|
|
7474
|
+
// This would be fetched from the practitioner profile
|
|
7475
|
+
displayName: "Dr. Practitioner",
|
|
7476
|
+
// This would be fetched from the practitioner profile
|
|
7477
|
+
responseStatus: "accepted"
|
|
7478
|
+
});
|
|
7479
|
+
}
|
|
7480
|
+
if (calendarEvent.patientProfileId) {
|
|
7481
|
+
googleEvent.attendees.push({
|
|
7482
|
+
email: "patient@example.com",
|
|
7483
|
+
// This would be fetched from the patient profile
|
|
7484
|
+
displayName: "Patient",
|
|
7485
|
+
// This would be fetched from the patient profile
|
|
7486
|
+
responseStatus: "needsAction"
|
|
7487
|
+
});
|
|
7488
|
+
}
|
|
7489
|
+
}
|
|
7490
|
+
return googleEvent;
|
|
7491
|
+
}
|
|
7492
|
+
async function deleteGoogleCalendarEventUtil(db, entityType, entityId, syncedCalendar, eventId) {
|
|
7493
|
+
try {
|
|
7494
|
+
const accessToken = await ensureValidToken(
|
|
7495
|
+
db,
|
|
7496
|
+
entityType,
|
|
7497
|
+
entityId,
|
|
7498
|
+
syncedCalendar
|
|
7499
|
+
);
|
|
7500
|
+
await makeRequest(
|
|
7501
|
+
"delete",
|
|
7502
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${eventId}`,
|
|
7503
|
+
{ Authorization: `Bearer ${accessToken}` }
|
|
7504
|
+
);
|
|
7505
|
+
return true;
|
|
7506
|
+
} catch (error) {
|
|
7507
|
+
const apiError = error;
|
|
7508
|
+
console.error(
|
|
7509
|
+
"Error deleting event from Google Calendar:",
|
|
7510
|
+
apiError.message || "Unknown error"
|
|
7511
|
+
);
|
|
7512
|
+
throw new Error(
|
|
7513
|
+
`Failed to delete event from Google Calendar: ${apiError.message || "Unknown error"}`
|
|
7514
|
+
);
|
|
7515
|
+
}
|
|
7516
|
+
}
|
|
7517
|
+
function getGoogleCalendarOAuthUrlUtil(scopes = ["https://www.googleapis.com/auth/calendar"]) {
|
|
7518
|
+
const scopeString = encodeURIComponent(scopes.join(" "));
|
|
7519
|
+
return `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(
|
|
7520
|
+
REDIRECT_URI
|
|
7521
|
+
)}&response_type=code&scope=${scopeString}&access_type=offline&prompt=consent`;
|
|
7522
|
+
}
|
|
7523
|
+
|
|
7524
|
+
// src/services/calendar/synced-calendars.service.ts
|
|
7525
|
+
var SyncedCalendarsService = class extends BaseService {
|
|
7526
|
+
/**
|
|
7527
|
+
* Creates a new SyncedCalendarsService instance
|
|
7528
|
+
* @param db - Firestore instance
|
|
7529
|
+
* @param auth - Firebase Auth instance
|
|
7530
|
+
* @param app - Firebase App instance
|
|
7531
|
+
*/
|
|
7532
|
+
constructor(db, auth, app) {
|
|
7533
|
+
super(db, auth, app);
|
|
7534
|
+
}
|
|
7535
|
+
// ===== Practitioner Synced Calendars =====
|
|
7536
|
+
/**
|
|
7537
|
+
* Creates a synced calendar for a practitioner
|
|
7538
|
+
* @param practitionerId - ID of the practitioner
|
|
7539
|
+
* @param calendarData - Synced calendar data
|
|
7540
|
+
* @returns Created synced calendar
|
|
7541
|
+
*/
|
|
7542
|
+
async createPractitionerSyncedCalendar(practitionerId, calendarData) {
|
|
7543
|
+
return createPractitionerSyncedCalendarUtil(
|
|
7544
|
+
this.db,
|
|
7545
|
+
practitionerId,
|
|
7546
|
+
calendarData,
|
|
7547
|
+
this.generateId.bind(this)
|
|
7548
|
+
);
|
|
7549
|
+
}
|
|
7550
|
+
/**
|
|
7551
|
+
* Gets a synced calendar for a practitioner
|
|
7552
|
+
* @param practitionerId - ID of the practitioner
|
|
7553
|
+
* @param calendarId - ID of the synced calendar
|
|
7554
|
+
* @returns Synced calendar or null if not found
|
|
7555
|
+
*/
|
|
7556
|
+
async getPractitionerSyncedCalendar(practitionerId, calendarId) {
|
|
7557
|
+
return getPractitionerSyncedCalendarUtil(
|
|
7558
|
+
this.db,
|
|
7559
|
+
practitionerId,
|
|
7560
|
+
calendarId
|
|
7561
|
+
);
|
|
7562
|
+
}
|
|
7563
|
+
/**
|
|
7564
|
+
* Gets all synced calendars for a practitioner
|
|
7565
|
+
* @param practitionerId - ID of the practitioner
|
|
7566
|
+
* @returns Array of synced calendars
|
|
7567
|
+
*/
|
|
7568
|
+
async getPractitionerSyncedCalendars(practitionerId) {
|
|
7569
|
+
return getPractitionerSyncedCalendarsUtil(this.db, practitionerId);
|
|
7570
|
+
}
|
|
7571
|
+
/**
|
|
7572
|
+
* Updates a synced calendar for a practitioner
|
|
7573
|
+
* @param practitionerId - ID of the practitioner
|
|
7574
|
+
* @param calendarId - ID of the synced calendar
|
|
7575
|
+
* @param updateData - Data to update
|
|
7576
|
+
* @returns Updated synced calendar
|
|
7577
|
+
*/
|
|
7578
|
+
async updatePractitionerSyncedCalendar(practitionerId, calendarId, updateData) {
|
|
7579
|
+
return updatePractitionerSyncedCalendarUtil(
|
|
7580
|
+
this.db,
|
|
7581
|
+
practitionerId,
|
|
7582
|
+
calendarId,
|
|
7583
|
+
updateData
|
|
7584
|
+
);
|
|
7585
|
+
}
|
|
7586
|
+
/**
|
|
7587
|
+
* Deletes a synced calendar for a practitioner
|
|
7588
|
+
* @param practitionerId - ID of the practitioner
|
|
7589
|
+
* @param calendarId - ID of the synced calendar
|
|
7590
|
+
*/
|
|
7591
|
+
async deletePractitionerSyncedCalendar(practitionerId, calendarId) {
|
|
7592
|
+
return deletePractitionerSyncedCalendarUtil(
|
|
7593
|
+
this.db,
|
|
7594
|
+
practitionerId,
|
|
7595
|
+
calendarId
|
|
7596
|
+
);
|
|
7597
|
+
}
|
|
7598
|
+
// ===== Patient Synced Calendars =====
|
|
7599
|
+
/**
|
|
7600
|
+
* Creates a synced calendar for a patient
|
|
7601
|
+
* @param patientId - ID of the patient
|
|
7602
|
+
* @param calendarData - Synced calendar data
|
|
7603
|
+
* @returns Created synced calendar
|
|
7604
|
+
*/
|
|
7605
|
+
async createPatientSyncedCalendar(patientId, calendarData) {
|
|
7606
|
+
return createPatientSyncedCalendarUtil(
|
|
7607
|
+
this.db,
|
|
7608
|
+
patientId,
|
|
7609
|
+
calendarData,
|
|
7610
|
+
this.generateId.bind(this)
|
|
7611
|
+
);
|
|
7612
|
+
}
|
|
7613
|
+
/**
|
|
7614
|
+
* Gets a synced calendar for a patient
|
|
7615
|
+
* @param patientId - ID of the patient
|
|
7616
|
+
* @param calendarId - ID of the synced calendar
|
|
7617
|
+
* @returns Synced calendar or null if not found
|
|
7618
|
+
*/
|
|
7619
|
+
async getPatientSyncedCalendar(patientId, calendarId) {
|
|
7620
|
+
return getPatientSyncedCalendarUtil(this.db, patientId, calendarId);
|
|
7621
|
+
}
|
|
7622
|
+
/**
|
|
7623
|
+
* Gets all synced calendars for a patient
|
|
7624
|
+
* @param patientId - ID of the patient
|
|
7625
|
+
* @returns Array of synced calendars
|
|
7626
|
+
*/
|
|
7627
|
+
async getPatientSyncedCalendars(patientId) {
|
|
7628
|
+
return getPatientSyncedCalendarsUtil(this.db, patientId);
|
|
7629
|
+
}
|
|
7630
|
+
/**
|
|
7631
|
+
* Updates a synced calendar for a patient
|
|
7632
|
+
* @param patientId - ID of the patient
|
|
7633
|
+
* @param calendarId - ID of the synced calendar
|
|
7634
|
+
* @param updateData - Data to update
|
|
7635
|
+
* @returns Updated synced calendar
|
|
7636
|
+
*/
|
|
7637
|
+
async updatePatientSyncedCalendar(patientId, calendarId, updateData) {
|
|
7638
|
+
return updatePatientSyncedCalendarUtil(
|
|
7639
|
+
this.db,
|
|
7640
|
+
patientId,
|
|
7641
|
+
calendarId,
|
|
7642
|
+
updateData
|
|
7643
|
+
);
|
|
7644
|
+
}
|
|
7645
|
+
/**
|
|
7646
|
+
* Deletes a synced calendar for a patient
|
|
7647
|
+
* @param patientId - ID of the patient
|
|
7648
|
+
* @param calendarId - ID of the synced calendar
|
|
7649
|
+
*/
|
|
7650
|
+
async deletePatientSyncedCalendar(patientId, calendarId) {
|
|
7651
|
+
return deletePatientSyncedCalendarUtil(this.db, patientId, calendarId);
|
|
7652
|
+
}
|
|
7653
|
+
// ===== Clinic Synced Calendars =====
|
|
7654
|
+
/**
|
|
7655
|
+
* Creates a synced calendar for a clinic
|
|
7656
|
+
* @param clinicId - ID of the clinic
|
|
7657
|
+
* @param calendarData - Synced calendar data
|
|
7658
|
+
* @returns Created synced calendar
|
|
7659
|
+
*/
|
|
7660
|
+
async createClinicSyncedCalendar(clinicId, calendarData) {
|
|
7661
|
+
return createClinicSyncedCalendarUtil(
|
|
7662
|
+
this.db,
|
|
7663
|
+
clinicId,
|
|
7664
|
+
calendarData,
|
|
7665
|
+
this.generateId.bind(this)
|
|
7666
|
+
);
|
|
7667
|
+
}
|
|
7668
|
+
/**
|
|
7669
|
+
* Gets a synced calendar for a clinic
|
|
7670
|
+
* @param clinicId - ID of the clinic
|
|
7671
|
+
* @param calendarId - ID of the synced calendar
|
|
7672
|
+
* @returns Synced calendar or null if not found
|
|
7673
|
+
*/
|
|
7674
|
+
async getClinicSyncedCalendar(clinicId, calendarId) {
|
|
7675
|
+
return getClinicSyncedCalendarUtil(this.db, clinicId, calendarId);
|
|
7676
|
+
}
|
|
7677
|
+
/**
|
|
7678
|
+
* Gets all synced calendars for a clinic
|
|
7679
|
+
* @param clinicId - ID of the clinic
|
|
7680
|
+
* @returns Array of synced calendars
|
|
7681
|
+
*/
|
|
7682
|
+
async getClinicSyncedCalendars(clinicId) {
|
|
7683
|
+
return getClinicSyncedCalendarsUtil(this.db, clinicId);
|
|
7684
|
+
}
|
|
7685
|
+
/**
|
|
7686
|
+
* Updates a synced calendar for a clinic
|
|
7687
|
+
* @param clinicId - ID of the clinic
|
|
7688
|
+
* @param calendarId - ID of the synced calendar
|
|
7689
|
+
* @param updateData - Data to update
|
|
7690
|
+
* @returns Updated synced calendar
|
|
7691
|
+
*/
|
|
7692
|
+
async updateClinicSyncedCalendar(clinicId, calendarId, updateData) {
|
|
7693
|
+
return updateClinicSyncedCalendarUtil(
|
|
7694
|
+
this.db,
|
|
7695
|
+
clinicId,
|
|
7696
|
+
calendarId,
|
|
7697
|
+
updateData
|
|
7698
|
+
);
|
|
7699
|
+
}
|
|
7700
|
+
/**
|
|
7701
|
+
* Deletes a synced calendar for a clinic
|
|
7702
|
+
* @param clinicId - ID of the clinic
|
|
7703
|
+
* @param calendarId - ID of the synced calendar
|
|
7704
|
+
*/
|
|
7705
|
+
async deleteClinicSyncedCalendar(clinicId, calendarId) {
|
|
7706
|
+
return deleteClinicSyncedCalendarUtil(this.db, clinicId, calendarId);
|
|
7707
|
+
}
|
|
7708
|
+
// ===== Google Calendar Integration =====
|
|
7709
|
+
/**
|
|
7710
|
+
* Gets the OAuth URL for Google Calendar
|
|
7711
|
+
* @param scopes - OAuth scopes to request
|
|
7712
|
+
* @returns OAuth URL
|
|
7713
|
+
*/
|
|
7714
|
+
getGoogleCalendarOAuthUrl(scopes = ["https://www.googleapis.com/auth/calendar"]) {
|
|
7715
|
+
return getGoogleCalendarOAuthUrlUtil(scopes);
|
|
7716
|
+
}
|
|
7717
|
+
/**
|
|
7718
|
+
* Authenticates with Google Calendar using an authorization code
|
|
7719
|
+
* @param authCode - Authorization code from Google OAuth
|
|
7720
|
+
* @returns Access token, refresh token, and expiration time
|
|
7721
|
+
*/
|
|
7722
|
+
async authenticateWithGoogleCalendar(authCode) {
|
|
7723
|
+
return authenticateWithGoogleCalendarUtil(authCode);
|
|
7724
|
+
}
|
|
7725
|
+
/**
|
|
7726
|
+
* Lists available Google Calendars for a user
|
|
7727
|
+
* @param accessToken - Google API access token
|
|
7728
|
+
* @returns List of available calendars
|
|
7729
|
+
*/
|
|
7730
|
+
async listGoogleCalendars(accessToken) {
|
|
7731
|
+
return listGoogleCalendarsUtil(accessToken);
|
|
7732
|
+
}
|
|
7733
|
+
/**
|
|
7734
|
+
* Syncs events from our system to Google Calendar for a practitioner
|
|
7735
|
+
* @param practitionerId - ID of the practitioner
|
|
7736
|
+
* @param calendarId - ID of the synced calendar
|
|
7737
|
+
* @param events - Events to sync
|
|
7738
|
+
* @param existingSyncId - Optional existing sync ID for updating an event
|
|
7739
|
+
* @returns Result of the sync operation
|
|
7740
|
+
*/
|
|
7741
|
+
async syncPractitionerEventsToGoogleCalendar(practitionerId, calendarId, events, existingSyncId) {
|
|
7742
|
+
const syncedCalendar = await this.getPractitionerSyncedCalendar(
|
|
7743
|
+
practitionerId,
|
|
7744
|
+
calendarId
|
|
7745
|
+
);
|
|
7746
|
+
if (!syncedCalendar) {
|
|
7747
|
+
throw new Error("Synced calendar not found");
|
|
7748
|
+
}
|
|
7749
|
+
return syncEventsToGoogleCalendarUtil(
|
|
7750
|
+
this.db,
|
|
7751
|
+
"practitioner",
|
|
7752
|
+
practitionerId,
|
|
7753
|
+
syncedCalendar,
|
|
7754
|
+
events,
|
|
7755
|
+
existingSyncId
|
|
7756
|
+
);
|
|
7757
|
+
}
|
|
7758
|
+
/**
|
|
7759
|
+
* Syncs events from our system to Google Calendar for a patient
|
|
7760
|
+
* @param patientId - ID of the patient
|
|
7761
|
+
* @param calendarId - ID of the synced calendar
|
|
7762
|
+
* @param events - Events to sync
|
|
7763
|
+
* @param existingSyncId - Optional existing sync ID for updating an event
|
|
7764
|
+
* @returns Result of the sync operation
|
|
7765
|
+
*/
|
|
7766
|
+
async syncPatientEventsToGoogleCalendar(patientId, calendarId, events, existingSyncId) {
|
|
7767
|
+
const syncedCalendar = await this.getPatientSyncedCalendar(
|
|
7768
|
+
patientId,
|
|
7769
|
+
calendarId
|
|
7770
|
+
);
|
|
7771
|
+
if (!syncedCalendar) {
|
|
7772
|
+
throw new Error("Synced calendar not found");
|
|
7773
|
+
}
|
|
7774
|
+
return syncEventsToGoogleCalendarUtil(
|
|
7775
|
+
this.db,
|
|
7776
|
+
"patient",
|
|
7777
|
+
patientId,
|
|
7778
|
+
syncedCalendar,
|
|
7779
|
+
events,
|
|
7780
|
+
existingSyncId
|
|
7781
|
+
);
|
|
7782
|
+
}
|
|
7783
|
+
/**
|
|
7784
|
+
* Syncs events from our system to Google Calendar for a clinic
|
|
7785
|
+
* @param clinicId - ID of the clinic
|
|
7786
|
+
* @param calendarId - ID of the synced calendar
|
|
7787
|
+
* @param events - Events to sync
|
|
7788
|
+
* @returns Result of the sync operation
|
|
7789
|
+
*/
|
|
7790
|
+
async syncClinicEventsToGoogleCalendar(clinicId, calendarId, events) {
|
|
7791
|
+
const syncedCalendar = await this.getClinicSyncedCalendar(
|
|
7792
|
+
clinicId,
|
|
7793
|
+
calendarId
|
|
7794
|
+
);
|
|
7795
|
+
if (!syncedCalendar) {
|
|
7796
|
+
throw new Error("Synced calendar not found");
|
|
7797
|
+
}
|
|
7798
|
+
return syncEventsToGoogleCalendarUtil(
|
|
7799
|
+
this.db,
|
|
7800
|
+
"clinic",
|
|
7801
|
+
clinicId,
|
|
7802
|
+
syncedCalendar,
|
|
7803
|
+
events
|
|
7804
|
+
);
|
|
7805
|
+
}
|
|
7806
|
+
/**
|
|
7807
|
+
* Fetches events from Google Calendar for a practitioner
|
|
7808
|
+
* @param practitionerId - ID of the practitioner
|
|
7809
|
+
* @param calendarId - ID of the synced calendar
|
|
7810
|
+
* @param startDate - Start date for fetching events
|
|
7811
|
+
* @param endDate - End date for fetching events
|
|
7812
|
+
* @returns Events fetched from Google Calendar
|
|
7813
|
+
*/
|
|
7814
|
+
async fetchEventsFromPractitionerGoogleCalendar(practitionerId, calendarId, startDate, endDate) {
|
|
7815
|
+
const syncedCalendar = await this.getPractitionerSyncedCalendar(
|
|
7816
|
+
practitionerId,
|
|
7817
|
+
calendarId
|
|
7818
|
+
);
|
|
7819
|
+
if (!syncedCalendar) {
|
|
7820
|
+
throw new Error("Synced calendar not found");
|
|
7821
|
+
}
|
|
7822
|
+
return fetchEventsFromGoogleCalendarUtil(
|
|
7823
|
+
this.db,
|
|
7824
|
+
"practitioner",
|
|
7825
|
+
practitionerId,
|
|
7826
|
+
syncedCalendar,
|
|
7827
|
+
startDate,
|
|
7828
|
+
endDate
|
|
7829
|
+
);
|
|
7830
|
+
}
|
|
7831
|
+
/**
|
|
7832
|
+
* Fetches events from Google Calendar for a patient
|
|
7833
|
+
* @param patientId - ID of the patient
|
|
7834
|
+
* @param calendarId - ID of the synced calendar
|
|
7835
|
+
* @param startDate - Start date for fetching events
|
|
7836
|
+
* @param endDate - End date for fetching events
|
|
7837
|
+
* @returns Events fetched from Google Calendar
|
|
7838
|
+
*/
|
|
7839
|
+
async fetchEventsFromPatientGoogleCalendar(patientId, calendarId, startDate, endDate) {
|
|
7840
|
+
const syncedCalendar = await this.getPatientSyncedCalendar(
|
|
7841
|
+
patientId,
|
|
7842
|
+
calendarId
|
|
7843
|
+
);
|
|
7844
|
+
if (!syncedCalendar) {
|
|
7845
|
+
throw new Error("Synced calendar not found");
|
|
7846
|
+
}
|
|
7847
|
+
return fetchEventsFromGoogleCalendarUtil(
|
|
7848
|
+
this.db,
|
|
7849
|
+
"patient",
|
|
7850
|
+
patientId,
|
|
7851
|
+
syncedCalendar,
|
|
7852
|
+
startDate,
|
|
7853
|
+
endDate
|
|
7854
|
+
);
|
|
7855
|
+
}
|
|
7856
|
+
/**
|
|
7857
|
+
* Fetches events from Google Calendar for a clinic
|
|
7858
|
+
* @param clinicId - ID of the clinic
|
|
7859
|
+
* @param calendarId - ID of the synced calendar
|
|
7860
|
+
* @param startDate - Start date for fetching events
|
|
7861
|
+
* @param endDate - End date for fetching events
|
|
7862
|
+
* @returns Events fetched from Google Calendar
|
|
7863
|
+
*/
|
|
7864
|
+
async fetchEventsFromClinicGoogleCalendar(clinicId, calendarId, startDate, endDate) {
|
|
7865
|
+
const syncedCalendar = await this.getClinicSyncedCalendar(
|
|
7866
|
+
clinicId,
|
|
7867
|
+
calendarId
|
|
7868
|
+
);
|
|
7869
|
+
if (!syncedCalendar) {
|
|
7870
|
+
throw new Error("Synced calendar not found");
|
|
7871
|
+
}
|
|
7872
|
+
return fetchEventsFromGoogleCalendarUtil(
|
|
7873
|
+
this.db,
|
|
7874
|
+
"clinic",
|
|
7875
|
+
clinicId,
|
|
7876
|
+
syncedCalendar,
|
|
7877
|
+
startDate,
|
|
7878
|
+
endDate
|
|
7879
|
+
);
|
|
7880
|
+
}
|
|
7881
|
+
/**
|
|
7882
|
+
* Deletes an event from Google Calendar for a practitioner
|
|
7883
|
+
* @param practitionerId - ID of the practitioner
|
|
7884
|
+
* @param calendarId - ID of the synced calendar
|
|
7885
|
+
* @param eventId - ID of the event in Google Calendar
|
|
7886
|
+
* @returns Success status
|
|
7887
|
+
*/
|
|
7888
|
+
async deletePractitionerGoogleCalendarEvent(practitionerId, calendarId, eventId) {
|
|
7889
|
+
const syncedCalendar = await this.getPractitionerSyncedCalendar(
|
|
7890
|
+
practitionerId,
|
|
7891
|
+
calendarId
|
|
7892
|
+
);
|
|
7893
|
+
if (!syncedCalendar) {
|
|
7894
|
+
throw new Error("Synced calendar not found");
|
|
7895
|
+
}
|
|
7896
|
+
return deleteGoogleCalendarEventUtil(
|
|
7897
|
+
this.db,
|
|
7898
|
+
"practitioner",
|
|
7899
|
+
practitionerId,
|
|
7900
|
+
syncedCalendar,
|
|
7901
|
+
eventId
|
|
7902
|
+
);
|
|
7903
|
+
}
|
|
7904
|
+
/**
|
|
7905
|
+
* Deletes an event from Google Calendar for a patient
|
|
7906
|
+
* @param patientId - ID of the patient
|
|
7907
|
+
* @param calendarId - ID of the synced calendar
|
|
7908
|
+
* @param eventId - ID of the event in Google Calendar
|
|
7909
|
+
* @returns Success status
|
|
7910
|
+
*/
|
|
7911
|
+
async deletePatientGoogleCalendarEvent(patientId, calendarId, eventId) {
|
|
7912
|
+
const syncedCalendar = await this.getPatientSyncedCalendar(
|
|
7913
|
+
patientId,
|
|
7914
|
+
calendarId
|
|
7915
|
+
);
|
|
7916
|
+
if (!syncedCalendar) {
|
|
7917
|
+
throw new Error("Synced calendar not found");
|
|
7918
|
+
}
|
|
7919
|
+
return deleteGoogleCalendarEventUtil(
|
|
7920
|
+
this.db,
|
|
7921
|
+
"patient",
|
|
7922
|
+
patientId,
|
|
7923
|
+
syncedCalendar,
|
|
7924
|
+
eventId
|
|
7925
|
+
);
|
|
7926
|
+
}
|
|
7927
|
+
/**
|
|
7928
|
+
* Deletes an event from Google Calendar for a clinic
|
|
7929
|
+
* @param clinicId - ID of the clinic
|
|
7930
|
+
* @param calendarId - ID of the synced calendar
|
|
7931
|
+
* @param eventId - ID of the event in Google Calendar
|
|
7932
|
+
* @returns Success status
|
|
7933
|
+
*/
|
|
7934
|
+
async deleteClinicGoogleCalendarEvent(clinicId, calendarId, eventId) {
|
|
7935
|
+
const syncedCalendar = await this.getClinicSyncedCalendar(
|
|
7936
|
+
clinicId,
|
|
7937
|
+
calendarId
|
|
7938
|
+
);
|
|
7939
|
+
if (!syncedCalendar) {
|
|
7940
|
+
throw new Error("Synced calendar not found");
|
|
7941
|
+
}
|
|
7942
|
+
return deleteGoogleCalendarEventUtil(
|
|
7943
|
+
this.db,
|
|
7944
|
+
"clinic",
|
|
7945
|
+
clinicId,
|
|
7946
|
+
syncedCalendar,
|
|
7947
|
+
eventId
|
|
7948
|
+
);
|
|
7949
|
+
}
|
|
7950
|
+
/**
|
|
7951
|
+
* Converts Google Calendar events to our system's format for a practitioner
|
|
7952
|
+
* @param practitionerId - ID of the practitioner
|
|
7953
|
+
* @param googleEvents - Google Calendar events
|
|
7954
|
+
* @returns Converted calendar events
|
|
7955
|
+
*/
|
|
7956
|
+
convertGoogleEventsToPractitionerEvents(practitionerId, googleEvents) {
|
|
7957
|
+
return googleEvents.map(
|
|
7958
|
+
(event) => convertGoogleEventToCalendarEventUtil(
|
|
7959
|
+
event,
|
|
7960
|
+
practitionerId,
|
|
7961
|
+
"practitioner"
|
|
7962
|
+
)
|
|
7963
|
+
);
|
|
7964
|
+
}
|
|
7965
|
+
/**
|
|
7966
|
+
* Converts Google Calendar events to our system's format for a patient
|
|
7967
|
+
* @param patientId - ID of the patient
|
|
7968
|
+
* @param googleEvents - Google Calendar events
|
|
7969
|
+
* @returns Converted calendar events
|
|
7970
|
+
*/
|
|
7971
|
+
convertGoogleEventsToPatientEvents(patientId, googleEvents) {
|
|
7972
|
+
return googleEvents.map(
|
|
7973
|
+
(event) => convertGoogleEventToCalendarEventUtil(event, patientId, "patient")
|
|
7974
|
+
);
|
|
7975
|
+
}
|
|
7976
|
+
/**
|
|
7977
|
+
* Converts Google Calendar events to our system's format for a clinic
|
|
7978
|
+
* @param clinicId - ID of the clinic
|
|
7979
|
+
* @param googleEvents - Google Calendar events
|
|
7980
|
+
* @returns Converted calendar events
|
|
7981
|
+
*/
|
|
7982
|
+
convertGoogleEventsToClinicEvents(clinicId, googleEvents) {
|
|
7983
|
+
return googleEvents.map(
|
|
7984
|
+
(event) => convertGoogleEventToCalendarEventUtil(event, clinicId, "clinic")
|
|
7985
|
+
);
|
|
7986
|
+
}
|
|
7987
|
+
/**
|
|
7988
|
+
* Fetches a single event from Google Calendar for a practitioner
|
|
7989
|
+
* @param practitionerId - ID of the practitioner
|
|
7990
|
+
* @param calendarId - ID of the synced calendar
|
|
7991
|
+
* @param eventId - ID of the event in Google Calendar
|
|
7992
|
+
* @returns The event data or null if not found
|
|
7993
|
+
*/
|
|
7994
|
+
async fetchEventFromPractitionerGoogleCalendar(practitionerId, calendarId, eventId) {
|
|
7995
|
+
var _a;
|
|
7996
|
+
const syncedCalendar = await this.getPractitionerSyncedCalendar(
|
|
7997
|
+
practitionerId,
|
|
7998
|
+
calendarId
|
|
7999
|
+
);
|
|
8000
|
+
if (!syncedCalendar) {
|
|
8001
|
+
throw new Error("Synced calendar not found");
|
|
8002
|
+
}
|
|
8003
|
+
try {
|
|
8004
|
+
const { accessToken } = await refreshGoogleCalendarTokenUtil(
|
|
8005
|
+
syncedCalendar.refreshToken
|
|
8006
|
+
);
|
|
8007
|
+
const response = await makeRequest(
|
|
8008
|
+
"get",
|
|
8009
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${eventId}`,
|
|
8010
|
+
{ Authorization: `Bearer ${accessToken}` }
|
|
8011
|
+
);
|
|
8012
|
+
await updateLastSyncedTimestampUtil(
|
|
8013
|
+
this.db,
|
|
8014
|
+
"practitioner",
|
|
8015
|
+
practitionerId,
|
|
8016
|
+
syncedCalendar.id
|
|
8017
|
+
);
|
|
8018
|
+
return response;
|
|
8019
|
+
} catch (error) {
|
|
8020
|
+
if (((_a = error.response) == null ? void 0 : _a.status) === 404) {
|
|
8021
|
+
return null;
|
|
8022
|
+
}
|
|
8023
|
+
console.error(
|
|
8024
|
+
`Error fetching event from Google Calendar: ${error.message}`
|
|
8025
|
+
);
|
|
8026
|
+
throw error;
|
|
8027
|
+
}
|
|
8028
|
+
}
|
|
8029
|
+
};
|
|
8030
|
+
|
|
8031
|
+
// src/services/calendar/calendar-refactored.service.ts
|
|
8032
|
+
var MIN_APPOINTMENT_DURATION2 = 15;
|
|
8033
|
+
var CalendarServiceV2 = class extends BaseService {
|
|
8034
|
+
/**
|
|
8035
|
+
* Creates a new CalendarService instance
|
|
8036
|
+
* @param db - Firestore instance
|
|
8037
|
+
* @param auth - Firebase Auth instance
|
|
8038
|
+
* @param app - Firebase App instance
|
|
8039
|
+
*/
|
|
8040
|
+
constructor(db, auth, app) {
|
|
8041
|
+
super(db, auth, app);
|
|
8042
|
+
this.syncedCalendarsService = new SyncedCalendarsService(db, auth, app);
|
|
8043
|
+
}
|
|
8044
|
+
// #region Public API Methods
|
|
8045
|
+
/**
|
|
8046
|
+
* Creates a new appointment with proper validation and scheduling rules
|
|
8047
|
+
* @param params - Appointment creation parameters
|
|
8048
|
+
* @returns Created calendar event
|
|
8049
|
+
*/
|
|
8050
|
+
async createAppointment(params) {
|
|
8051
|
+
await this.validateAppointmentParams(params);
|
|
8052
|
+
await this.validateClinicWorkingHours(params.clinicId, params.eventTime);
|
|
8053
|
+
await this.validateDoctorAvailability(
|
|
8054
|
+
params.doctorId,
|
|
8055
|
+
params.eventTime,
|
|
8056
|
+
params.clinicId
|
|
8057
|
+
);
|
|
8058
|
+
const { clinicInfo, practitionerInfo, patientInfo } = await this.fetchProfileInfoCards(
|
|
8059
|
+
params.clinicId,
|
|
8060
|
+
params.doctorId,
|
|
8061
|
+
params.patientId
|
|
8062
|
+
);
|
|
8063
|
+
const appointmentData = {
|
|
8064
|
+
clinicBranchId: params.clinicId,
|
|
8065
|
+
clinicBranchInfo: clinicInfo,
|
|
8066
|
+
practitionerProfileId: params.doctorId,
|
|
8067
|
+
practitionerProfileInfo: practitionerInfo,
|
|
8068
|
+
patientProfileId: params.patientId,
|
|
8069
|
+
patientProfileInfo: patientInfo,
|
|
8070
|
+
procedureId: params.procedureId,
|
|
8071
|
+
eventLocation: params.eventLocation,
|
|
8072
|
+
eventName: "Appointment",
|
|
8073
|
+
// TODO: Add procedure name when procedure model is available
|
|
8074
|
+
eventTime: params.eventTime,
|
|
8075
|
+
description: params.description || "",
|
|
8076
|
+
status: "pending" /* PENDING */,
|
|
8077
|
+
syncStatus: "internal" /* INTERNAL */,
|
|
8078
|
+
eventType: "appointment" /* APPOINTMENT */
|
|
8079
|
+
};
|
|
8080
|
+
const appointment = await createAppointmentUtil(
|
|
8081
|
+
this.db,
|
|
8082
|
+
params.clinicId,
|
|
8083
|
+
params.doctorId,
|
|
8084
|
+
params.patientId,
|
|
8085
|
+
appointmentData,
|
|
8086
|
+
this.generateId.bind(this)
|
|
8087
|
+
);
|
|
8088
|
+
await this.syncAppointmentWithExternalCalendars(appointment);
|
|
8089
|
+
return appointment;
|
|
8090
|
+
}
|
|
8091
|
+
/**
|
|
8092
|
+
* Updates an existing appointment
|
|
8093
|
+
* @param params - Appointment update parameters
|
|
8094
|
+
* @returns Updated calendar event
|
|
8095
|
+
*/
|
|
8096
|
+
async updateAppointment(params) {
|
|
8097
|
+
await this.validateUpdatePermissions(params);
|
|
8098
|
+
const updateData = {
|
|
8099
|
+
eventTime: params.eventTime,
|
|
8100
|
+
description: params.description,
|
|
8101
|
+
status: params.status
|
|
8102
|
+
};
|
|
8103
|
+
const appointment = await updateAppointmentUtil(
|
|
8104
|
+
this.db,
|
|
8105
|
+
params.clinicId,
|
|
8106
|
+
params.doctorId,
|
|
8107
|
+
params.patientId,
|
|
8108
|
+
params.appointmentId,
|
|
8109
|
+
updateData
|
|
8110
|
+
);
|
|
8111
|
+
await this.syncAppointmentWithExternalCalendars(appointment);
|
|
8112
|
+
return appointment;
|
|
8113
|
+
}
|
|
8114
|
+
/**
|
|
8115
|
+
* Gets available appointment slots for a doctor at a clinic
|
|
8116
|
+
* @param clinicId - ID of the clinic
|
|
8117
|
+
* @param doctorId - ID of the doctor
|
|
8118
|
+
* @param date - Date to check availability for
|
|
8119
|
+
* @returns Array of available time slots
|
|
8120
|
+
*/
|
|
8121
|
+
async getAvailableSlots(clinicId, doctorId, date) {
|
|
8122
|
+
const workingHours = await this.getClinicWorkingHours(clinicId, date);
|
|
8123
|
+
const doctorSchedule = await this.getDoctorSchedule(doctorId, date);
|
|
8124
|
+
const existingAppointments = await this.getDoctorAppointments(
|
|
8125
|
+
doctorId,
|
|
8126
|
+
date
|
|
8127
|
+
);
|
|
8128
|
+
return this.calculateAvailableSlots(
|
|
8129
|
+
workingHours,
|
|
8130
|
+
doctorSchedule,
|
|
8131
|
+
existingAppointments
|
|
8132
|
+
);
|
|
8133
|
+
}
|
|
8134
|
+
/**
|
|
8135
|
+
* Confirms an appointment
|
|
8136
|
+
* @param appointmentId - ID of the appointment
|
|
8137
|
+
* @param clinicId - ID of the clinic
|
|
8138
|
+
* @returns Confirmed calendar event
|
|
8139
|
+
*/
|
|
8140
|
+
async confirmAppointment(appointmentId, clinicId) {
|
|
8141
|
+
return this.updateAppointmentStatus(
|
|
8142
|
+
appointmentId,
|
|
8143
|
+
clinicId,
|
|
8144
|
+
"confirmed" /* CONFIRMED */
|
|
8145
|
+
);
|
|
8146
|
+
}
|
|
8147
|
+
/**
|
|
8148
|
+
* Rejects an appointment
|
|
8149
|
+
* @param appointmentId - ID of the appointment
|
|
8150
|
+
* @param clinicId - ID of the clinic
|
|
8151
|
+
* @returns Rejected calendar event
|
|
8152
|
+
*/
|
|
8153
|
+
async rejectAppointment(appointmentId, clinicId) {
|
|
8154
|
+
return this.updateAppointmentStatus(
|
|
8155
|
+
appointmentId,
|
|
8156
|
+
clinicId,
|
|
8157
|
+
"rejected" /* REJECTED */
|
|
8158
|
+
);
|
|
8159
|
+
}
|
|
8160
|
+
/**
|
|
8161
|
+
* Cancels an appointment
|
|
8162
|
+
* @param appointmentId - ID of the appointment
|
|
8163
|
+
* @param clinicId - ID of the clinic
|
|
8164
|
+
* @returns Canceled calendar event
|
|
8165
|
+
*/
|
|
8166
|
+
async cancelAppointment(appointmentId, clinicId) {
|
|
8167
|
+
return this.updateAppointmentStatus(
|
|
8168
|
+
appointmentId,
|
|
8169
|
+
clinicId,
|
|
8170
|
+
"canceled" /* CANCELED */
|
|
8171
|
+
);
|
|
8172
|
+
}
|
|
8173
|
+
/**
|
|
8174
|
+
* Imports events from external calendars
|
|
8175
|
+
* @param entityType - Type of entity (practitioner or patient)
|
|
8176
|
+
* @param entityId - ID of the entity
|
|
8177
|
+
* @param startDate - Start date for fetching events
|
|
8178
|
+
* @param endDate - End date for fetching events
|
|
8179
|
+
* @returns Number of events imported
|
|
8180
|
+
*/
|
|
8181
|
+
async importEventsFromExternalCalendars(entityType, entityId, startDate, endDate) {
|
|
8182
|
+
if (entityType === "patient") {
|
|
8183
|
+
return 0;
|
|
8184
|
+
}
|
|
8185
|
+
const syncedCalendars = await this.syncedCalendarsService.getPractitionerSyncedCalendars(
|
|
8186
|
+
entityId
|
|
8187
|
+
);
|
|
8188
|
+
const activeCalendars = syncedCalendars.filter((cal) => cal.isActive);
|
|
8189
|
+
if (activeCalendars.length === 0) {
|
|
8190
|
+
return 0;
|
|
8191
|
+
}
|
|
8192
|
+
let importedEventsCount = 0;
|
|
8193
|
+
const currentTime = Timestamp23.now();
|
|
8194
|
+
for (const calendar of activeCalendars) {
|
|
8195
|
+
try {
|
|
8196
|
+
let externalEvents = [];
|
|
8197
|
+
if (calendar.provider === "google" /* GOOGLE */) {
|
|
8198
|
+
externalEvents = await this.syncedCalendarsService.fetchEventsFromPractitionerGoogleCalendar(
|
|
8199
|
+
entityId,
|
|
8200
|
+
calendar.id,
|
|
8201
|
+
startDate,
|
|
8202
|
+
endDate
|
|
8203
|
+
);
|
|
8204
|
+
}
|
|
8205
|
+
for (const externalEvent of externalEvents) {
|
|
8206
|
+
try {
|
|
8207
|
+
const convertedEvent = this.syncedCalendarsService.convertGoogleEventsToPractitionerEvents(
|
|
8208
|
+
entityId,
|
|
8209
|
+
[externalEvent]
|
|
8210
|
+
)[0];
|
|
8211
|
+
if (!convertedEvent.eventTime) {
|
|
8212
|
+
continue;
|
|
8213
|
+
}
|
|
8214
|
+
const eventData = {
|
|
8215
|
+
// Ensure all required fields are set
|
|
8216
|
+
eventName: convertedEvent.eventName || "External Event",
|
|
8217
|
+
eventTime: convertedEvent.eventTime,
|
|
8218
|
+
description: convertedEvent.description || "",
|
|
8219
|
+
status: "confirmed" /* CONFIRMED */,
|
|
8220
|
+
syncStatus: "external" /* EXTERNAL */,
|
|
8221
|
+
eventType: "blocking" /* BLOCKING */,
|
|
8222
|
+
practitionerProfileId: entityId,
|
|
8223
|
+
syncedCalendarEventId: [
|
|
8224
|
+
{
|
|
8225
|
+
eventId: externalEvent.id,
|
|
8226
|
+
syncedCalendarProvider: calendar.provider,
|
|
8227
|
+
syncedAt: currentTime
|
|
8228
|
+
}
|
|
8229
|
+
]
|
|
8230
|
+
};
|
|
8231
|
+
const doctorEvent = await this.createDoctorBlockingEvent(
|
|
8232
|
+
entityId,
|
|
8233
|
+
eventData
|
|
8234
|
+
);
|
|
8235
|
+
if (doctorEvent) {
|
|
8236
|
+
importedEventsCount++;
|
|
8237
|
+
}
|
|
8238
|
+
} catch (eventError) {
|
|
8239
|
+
console.error("Error importing event:", eventError);
|
|
8240
|
+
}
|
|
8241
|
+
}
|
|
8242
|
+
} catch (calendarError) {
|
|
8243
|
+
console.error(
|
|
8244
|
+
`Error fetching events from calendar ${calendar.id}:`,
|
|
8245
|
+
calendarError
|
|
8246
|
+
);
|
|
8247
|
+
}
|
|
8248
|
+
}
|
|
8249
|
+
return importedEventsCount;
|
|
8250
|
+
}
|
|
8251
|
+
/**
|
|
8252
|
+
* Creates a blocking event in a doctor's calendar
|
|
8253
|
+
* @param doctorId - ID of the doctor
|
|
8254
|
+
* @param eventData - Calendar event data
|
|
8255
|
+
* @returns Created calendar event
|
|
8256
|
+
*/
|
|
8257
|
+
async createDoctorBlockingEvent(doctorId, eventData) {
|
|
8258
|
+
try {
|
|
8259
|
+
const eventId = this.generateId();
|
|
8260
|
+
const eventRef = doc19(
|
|
8261
|
+
this.db,
|
|
8262
|
+
PRACTITIONERS_COLLECTION,
|
|
8263
|
+
doctorId,
|
|
8264
|
+
CALENDAR_COLLECTION,
|
|
8265
|
+
eventId
|
|
8266
|
+
);
|
|
8267
|
+
const newEvent = {
|
|
8268
|
+
id: eventId,
|
|
8269
|
+
...eventData,
|
|
8270
|
+
createdAt: serverTimestamp18(),
|
|
8271
|
+
updatedAt: serverTimestamp18()
|
|
8272
|
+
};
|
|
8273
|
+
await setDoc19(eventRef, newEvent);
|
|
8274
|
+
return {
|
|
8275
|
+
...newEvent,
|
|
8276
|
+
createdAt: Timestamp23.now(),
|
|
8277
|
+
updatedAt: Timestamp23.now()
|
|
8278
|
+
};
|
|
8279
|
+
} catch (error) {
|
|
8280
|
+
console.error(
|
|
8281
|
+
`Error creating blocking event for doctor ${doctorId}:`,
|
|
8282
|
+
error
|
|
8283
|
+
);
|
|
8284
|
+
return null;
|
|
8285
|
+
}
|
|
8286
|
+
}
|
|
8287
|
+
/**
|
|
8288
|
+
* Periodically syncs events from external calendars for doctors
|
|
8289
|
+
* This would be called via a scheduled Cloud Function
|
|
8290
|
+
* @param lookbackDays - Number of days to look back for events
|
|
8291
|
+
* @param lookforwardDays - Number of days to look forward for events
|
|
8292
|
+
*/
|
|
8293
|
+
async synchronizeExternalCalendars(lookbackDays = 7, lookforwardDays = 30) {
|
|
8294
|
+
try {
|
|
8295
|
+
const practitionersRef = collection17(this.db, PRACTITIONERS_COLLECTION);
|
|
8296
|
+
const practitionersSnapshot = await getDocs16(practitionersRef);
|
|
8297
|
+
const startDate = /* @__PURE__ */ new Date();
|
|
8298
|
+
startDate.setDate(startDate.getDate() - lookbackDays);
|
|
8299
|
+
const endDate = /* @__PURE__ */ new Date();
|
|
8300
|
+
endDate.setDate(endDate.getDate() + lookforwardDays);
|
|
8301
|
+
const syncPromises = [];
|
|
8302
|
+
for (const docSnapshot of practitionersSnapshot.docs) {
|
|
8303
|
+
const practitionerId = docSnapshot.id;
|
|
8304
|
+
syncPromises.push(
|
|
8305
|
+
this.importEventsFromExternalCalendars(
|
|
8306
|
+
"doctor",
|
|
8307
|
+
practitionerId,
|
|
8308
|
+
startDate,
|
|
8309
|
+
endDate
|
|
8310
|
+
).then((count) => {
|
|
8311
|
+
console.log(
|
|
8312
|
+
`Imported ${count} events for doctor ${practitionerId}`
|
|
8313
|
+
);
|
|
8314
|
+
}).catch((error) => {
|
|
8315
|
+
console.error(
|
|
8316
|
+
`Error importing events for doctor ${practitionerId}:`,
|
|
8317
|
+
error
|
|
8318
|
+
);
|
|
8319
|
+
})
|
|
8320
|
+
);
|
|
8321
|
+
syncPromises.push(
|
|
8322
|
+
this.updateExistingEventsFromExternalCalendars(
|
|
8323
|
+
practitionerId,
|
|
8324
|
+
startDate,
|
|
8325
|
+
endDate
|
|
8326
|
+
).then((count) => {
|
|
8327
|
+
console.log(
|
|
8328
|
+
`Updated ${count} events for doctor ${practitionerId}`
|
|
8329
|
+
);
|
|
8330
|
+
}).catch((error) => {
|
|
8331
|
+
console.error(
|
|
8332
|
+
`Error updating events for doctor ${practitionerId}:`,
|
|
8333
|
+
error
|
|
8334
|
+
);
|
|
8335
|
+
})
|
|
8336
|
+
);
|
|
8337
|
+
}
|
|
8338
|
+
await Promise.all(syncPromises);
|
|
8339
|
+
console.log("Completed external calendar synchronization");
|
|
8340
|
+
} catch (error) {
|
|
8341
|
+
console.error("Error synchronizing external calendars:", error);
|
|
8342
|
+
}
|
|
8343
|
+
}
|
|
8344
|
+
/**
|
|
8345
|
+
* Updates existing events that were synced from external calendars
|
|
8346
|
+
* @param doctorId - ID of the doctor
|
|
8347
|
+
* @param startDate - Start date for fetching events
|
|
8348
|
+
* @param endDate - End date for fetching events
|
|
8349
|
+
* @returns Number of events updated
|
|
8350
|
+
*/
|
|
8351
|
+
async updateExistingEventsFromExternalCalendars(doctorId, startDate, endDate) {
|
|
8352
|
+
var _a;
|
|
8353
|
+
try {
|
|
8354
|
+
const eventsRef = collection17(
|
|
8355
|
+
this.db,
|
|
8356
|
+
PRACTITIONERS_COLLECTION,
|
|
8357
|
+
doctorId,
|
|
8358
|
+
CALENDAR_COLLECTION
|
|
8359
|
+
);
|
|
8360
|
+
const q = query16(
|
|
8361
|
+
eventsRef,
|
|
8362
|
+
where16("syncStatus", "==", "external" /* EXTERNAL */),
|
|
8363
|
+
where16("eventTime.start", ">=", Timestamp23.fromDate(startDate)),
|
|
8364
|
+
where16("eventTime.start", "<=", Timestamp23.fromDate(endDate))
|
|
8365
|
+
);
|
|
8366
|
+
const eventsSnapshot = await getDocs16(q);
|
|
8367
|
+
const events = eventsSnapshot.docs.map((doc20) => ({
|
|
8368
|
+
id: doc20.id,
|
|
8369
|
+
...doc20.data()
|
|
8370
|
+
}));
|
|
8371
|
+
const calendars = await this.syncedCalendarsService.getPractitionerSyncedCalendars(
|
|
8372
|
+
doctorId
|
|
8373
|
+
);
|
|
8374
|
+
const activeCalendars = calendars.filter((cal) => cal.isActive);
|
|
8375
|
+
if (activeCalendars.length === 0 || events.length === 0) {
|
|
8376
|
+
return 0;
|
|
8377
|
+
}
|
|
8378
|
+
let updatedCount = 0;
|
|
8379
|
+
for (const event of events) {
|
|
8380
|
+
if (!((_a = event.syncedCalendarEventId) == null ? void 0 : _a.length)) continue;
|
|
8381
|
+
for (const syncId of event.syncedCalendarEventId) {
|
|
8382
|
+
const calendar = activeCalendars.find(
|
|
8383
|
+
(cal) => cal.provider === syncId.syncedCalendarProvider
|
|
8384
|
+
);
|
|
8385
|
+
if (!calendar) continue;
|
|
8386
|
+
if (syncId.syncedCalendarProvider === "google" /* GOOGLE */) {
|
|
8387
|
+
try {
|
|
8388
|
+
const externalEvent = await this.fetchExternalEvent(
|
|
8389
|
+
doctorId,
|
|
8390
|
+
calendar,
|
|
8391
|
+
syncId.eventId
|
|
8392
|
+
);
|
|
8393
|
+
if (externalEvent) {
|
|
8394
|
+
const externalStartTime = new Date(
|
|
8395
|
+
externalEvent.start.dateTime || externalEvent.start.date
|
|
8396
|
+
).getTime();
|
|
8397
|
+
const externalEndTime = new Date(
|
|
8398
|
+
externalEvent.end.dateTime || externalEvent.end.date
|
|
8399
|
+
).getTime();
|
|
8400
|
+
const localStartTime = event.eventTime.start.toDate().getTime();
|
|
8401
|
+
const localEndTime = event.eventTime.end.toDate().getTime();
|
|
8402
|
+
if (externalStartTime !== localStartTime || externalEndTime !== localEndTime || externalEvent.summary !== event.eventName || externalEvent.description !== event.description) {
|
|
8403
|
+
await this.updateLocalEventFromExternal(
|
|
8404
|
+
doctorId,
|
|
8405
|
+
event.id,
|
|
8406
|
+
externalEvent
|
|
8407
|
+
);
|
|
8408
|
+
updatedCount++;
|
|
8409
|
+
}
|
|
8410
|
+
} else {
|
|
8411
|
+
await this.updateEventStatus(
|
|
8412
|
+
doctorId,
|
|
8413
|
+
event.id,
|
|
8414
|
+
"canceled" /* CANCELED */
|
|
8415
|
+
);
|
|
8416
|
+
updatedCount++;
|
|
8417
|
+
}
|
|
8418
|
+
} catch (error) {
|
|
8419
|
+
console.error(
|
|
8420
|
+
`Error updating external event ${event.id}:`,
|
|
8421
|
+
error
|
|
8422
|
+
);
|
|
8423
|
+
}
|
|
8424
|
+
}
|
|
8425
|
+
}
|
|
8426
|
+
}
|
|
8427
|
+
return updatedCount;
|
|
8428
|
+
} catch (error) {
|
|
8429
|
+
console.error(
|
|
8430
|
+
"Error updating existing events from external calendars:",
|
|
8431
|
+
error
|
|
8432
|
+
);
|
|
8433
|
+
return 0;
|
|
8434
|
+
}
|
|
8435
|
+
}
|
|
8436
|
+
/**
|
|
8437
|
+
* Fetches a single external event from Google Calendar
|
|
8438
|
+
* @param doctorId - ID of the doctor
|
|
8439
|
+
* @param calendar - Calendar information
|
|
8440
|
+
* @param externalEventId - ID of the external event
|
|
8441
|
+
* @returns External event data or null if not found
|
|
8442
|
+
*/
|
|
8443
|
+
async fetchExternalEvent(doctorId, calendar, externalEventId) {
|
|
8444
|
+
try {
|
|
8445
|
+
if (calendar.provider === "google" /* GOOGLE */) {
|
|
8446
|
+
const result = await this.syncedCalendarsService.fetchEventFromPractitionerGoogleCalendar(
|
|
8447
|
+
doctorId,
|
|
8448
|
+
calendar.id,
|
|
8449
|
+
externalEventId
|
|
8450
|
+
);
|
|
8451
|
+
return result;
|
|
8452
|
+
}
|
|
8453
|
+
return null;
|
|
8454
|
+
} catch (error) {
|
|
8455
|
+
console.error(`Error fetching external event ${externalEventId}:`, error);
|
|
8456
|
+
return null;
|
|
8457
|
+
}
|
|
8458
|
+
}
|
|
8459
|
+
/**
|
|
8460
|
+
* Updates a local event with data from an external event
|
|
8461
|
+
* @param doctorId - ID of the doctor
|
|
8462
|
+
* @param eventId - ID of the local event
|
|
8463
|
+
* @param externalEvent - External event data
|
|
8464
|
+
*/
|
|
8465
|
+
async updateLocalEventFromExternal(doctorId, eventId, externalEvent) {
|
|
8466
|
+
try {
|
|
8467
|
+
const startTime = new Date(
|
|
8468
|
+
externalEvent.start.dateTime || externalEvent.start.date
|
|
8469
|
+
);
|
|
8470
|
+
const endTime = new Date(
|
|
8471
|
+
externalEvent.end.dateTime || externalEvent.end.date
|
|
8472
|
+
);
|
|
8473
|
+
const eventRef = doc19(
|
|
8474
|
+
this.db,
|
|
8475
|
+
PRACTITIONERS_COLLECTION,
|
|
8476
|
+
doctorId,
|
|
8477
|
+
CALENDAR_COLLECTION,
|
|
8478
|
+
eventId
|
|
8479
|
+
);
|
|
8480
|
+
await updateDoc20(eventRef, {
|
|
8481
|
+
eventName: externalEvent.summary || "External Event",
|
|
8482
|
+
eventTime: {
|
|
8483
|
+
start: Timestamp23.fromDate(startTime),
|
|
8484
|
+
end: Timestamp23.fromDate(endTime)
|
|
8485
|
+
},
|
|
8486
|
+
description: externalEvent.description || "",
|
|
8487
|
+
updatedAt: serverTimestamp18()
|
|
8488
|
+
});
|
|
8489
|
+
console.log(`Updated local event ${eventId} from external event`);
|
|
8490
|
+
} catch (error) {
|
|
8491
|
+
console.error(
|
|
8492
|
+
`Error updating local event ${eventId} from external:`,
|
|
8493
|
+
error
|
|
8494
|
+
);
|
|
8495
|
+
}
|
|
8496
|
+
}
|
|
8497
|
+
/**
|
|
8498
|
+
* Updates an event's status
|
|
8499
|
+
* @param doctorId - ID of the doctor
|
|
8500
|
+
* @param eventId - ID of the event
|
|
8501
|
+
* @param status - New status
|
|
8502
|
+
*/
|
|
8503
|
+
async updateEventStatus(doctorId, eventId, status) {
|
|
8504
|
+
try {
|
|
8505
|
+
const eventRef = doc19(
|
|
8506
|
+
this.db,
|
|
8507
|
+
PRACTITIONERS_COLLECTION,
|
|
8508
|
+
doctorId,
|
|
8509
|
+
CALENDAR_COLLECTION,
|
|
8510
|
+
eventId
|
|
8511
|
+
);
|
|
8512
|
+
await updateDoc20(eventRef, {
|
|
8513
|
+
status,
|
|
8514
|
+
updatedAt: serverTimestamp18()
|
|
8515
|
+
});
|
|
8516
|
+
console.log(`Updated event ${eventId} status to ${status}`);
|
|
8517
|
+
} catch (error) {
|
|
8518
|
+
console.error(`Error updating event ${eventId} status:`, error);
|
|
8519
|
+
}
|
|
8520
|
+
}
|
|
8521
|
+
/**
|
|
8522
|
+
* Creates a scheduled job to periodically sync external calendars
|
|
8523
|
+
* Note: This would be implemented using Cloud Functions in a real application
|
|
8524
|
+
* This is a sample implementation to show how it could be set up
|
|
8525
|
+
* @param interval - Interval in hours
|
|
8526
|
+
*/
|
|
8527
|
+
createScheduledSyncJob(interval = 3) {
|
|
8528
|
+
console.log(
|
|
8529
|
+
`Setting up scheduled calendar sync job every ${interval} hours`
|
|
8530
|
+
);
|
|
8531
|
+
}
|
|
8532
|
+
// #endregion
|
|
8533
|
+
// #region Private Helper Methods
|
|
8534
|
+
/**
|
|
8535
|
+
* Validates appointment creation parameters
|
|
8536
|
+
* @param params - Appointment parameters to validate
|
|
8537
|
+
* @throws Error if validation fails
|
|
8538
|
+
*/
|
|
8539
|
+
async validateAppointmentParams(params) {
|
|
8540
|
+
await createAppointmentSchema.parseAsync(params);
|
|
8541
|
+
}
|
|
8542
|
+
/**
|
|
8543
|
+
* Validates if the event time falls within clinic working hours
|
|
8544
|
+
* @param clinicId - ID of the clinic
|
|
8545
|
+
* @param eventTime - Event time to validate
|
|
8546
|
+
* @throws Error if validation fails
|
|
8547
|
+
*/
|
|
8548
|
+
async validateClinicWorkingHours(clinicId, eventTime) {
|
|
8549
|
+
const startDate = eventTime.start.toDate();
|
|
8550
|
+
const workingHours = await this.getClinicWorkingHours(clinicId, startDate);
|
|
8551
|
+
if (workingHours.length === 0) {
|
|
8552
|
+
throw new Error("Clinic is not open on this day");
|
|
8553
|
+
}
|
|
8554
|
+
const startTime = startDate;
|
|
8555
|
+
const endTime = eventTime.end.toDate();
|
|
8556
|
+
const isWithinWorkingHours = workingHours.some((slot) => {
|
|
8557
|
+
return slot.start <= startTime && slot.end >= endTime && slot.isAvailable;
|
|
8558
|
+
});
|
|
8559
|
+
if (!isWithinWorkingHours) {
|
|
8560
|
+
throw new Error("Appointment time is outside clinic working hours");
|
|
8561
|
+
}
|
|
8562
|
+
}
|
|
8563
|
+
/**
|
|
8564
|
+
* Validates if the doctor is available during the event time
|
|
8565
|
+
* @param doctorId - ID of the doctor
|
|
8566
|
+
* @param eventTime - Event time to validate
|
|
8567
|
+
* @param clinicId - ID of the clinic where the appointment is being booked
|
|
8568
|
+
* @throws Error if validation fails
|
|
8569
|
+
*/
|
|
8570
|
+
async validateDoctorAvailability(doctorId, eventTime, clinicId) {
|
|
8571
|
+
var _a;
|
|
8572
|
+
const startDate = eventTime.start.toDate();
|
|
8573
|
+
const startTime = startDate;
|
|
8574
|
+
const endTime = eventTime.end.toDate();
|
|
8575
|
+
const practitionerRef = doc19(this.db, PRACTITIONERS_COLLECTION, doctorId);
|
|
8576
|
+
const practitionerDoc = await getDoc22(practitionerRef);
|
|
8577
|
+
if (!practitionerDoc.exists()) {
|
|
8578
|
+
throw new Error(`Doctor with ID ${doctorId} not found`);
|
|
8579
|
+
}
|
|
8580
|
+
const practitioner = practitionerDoc.data();
|
|
8581
|
+
if (!practitioner.clinics.includes(clinicId)) {
|
|
8582
|
+
throw new Error("Doctor does not work at this clinic");
|
|
8583
|
+
}
|
|
8584
|
+
const clinicWorkingHours = (_a = practitioner.clinicWorkingHours) == null ? void 0 : _a.find(
|
|
8585
|
+
(hours) => hours.clinicId === clinicId && hours.isActive
|
|
8586
|
+
);
|
|
8587
|
+
if (!clinicWorkingHours) {
|
|
8588
|
+
throw new Error("Doctor does not have working hours set for this clinic");
|
|
8589
|
+
}
|
|
8590
|
+
const dayOfWeek = startDate.getDay();
|
|
8591
|
+
const dayKey = [
|
|
8592
|
+
"sunday",
|
|
8593
|
+
"monday",
|
|
8594
|
+
"tuesday",
|
|
8595
|
+
"wednesday",
|
|
8596
|
+
"thursday",
|
|
8597
|
+
"friday",
|
|
8598
|
+
"saturday"
|
|
8599
|
+
][dayOfWeek];
|
|
8600
|
+
const daySchedule = clinicWorkingHours.workingHours[dayKey];
|
|
8601
|
+
if (!daySchedule) {
|
|
8602
|
+
throw new Error("Doctor is not working on this day at this clinic");
|
|
8603
|
+
}
|
|
8604
|
+
const [startHour, startMinute] = daySchedule.start.split(":").map(Number);
|
|
8605
|
+
const [endHour, endMinute] = daySchedule.end.split(":").map(Number);
|
|
8606
|
+
const scheduleStart = new Date(startDate);
|
|
8607
|
+
scheduleStart.setHours(startHour, startMinute, 0, 0);
|
|
8608
|
+
const scheduleEnd = new Date(startDate);
|
|
8609
|
+
scheduleEnd.setHours(endHour, endMinute, 0, 0);
|
|
8610
|
+
if (startTime < scheduleStart || endTime > scheduleEnd) {
|
|
8611
|
+
throw new Error(
|
|
8612
|
+
"Appointment time is outside doctor's working hours at this clinic"
|
|
8613
|
+
);
|
|
8614
|
+
}
|
|
8615
|
+
const appointments = await this.getDoctorAppointments(doctorId, startDate);
|
|
8616
|
+
const hasOverlap = appointments.some((appointment) => {
|
|
8617
|
+
const appointmentStart = appointment.eventTime.start.toDate();
|
|
8618
|
+
const appointmentEnd = appointment.eventTime.end.toDate();
|
|
8619
|
+
return startTime >= appointmentStart && startTime < appointmentEnd || endTime > appointmentStart && endTime <= appointmentEnd || startTime <= appointmentStart && endTime >= appointmentEnd;
|
|
8620
|
+
});
|
|
8621
|
+
if (hasOverlap) {
|
|
8622
|
+
throw new Error("Doctor has another appointment during this time");
|
|
8623
|
+
}
|
|
8624
|
+
}
|
|
8625
|
+
/**
|
|
8626
|
+
* Updates appointment status
|
|
8627
|
+
* @param appointmentId - ID of the appointment
|
|
8628
|
+
* @param clinicId - ID of the clinic
|
|
8629
|
+
* @param status - New status
|
|
8630
|
+
* @returns Updated calendar event
|
|
8631
|
+
*/
|
|
8632
|
+
async updateAppointmentStatus(appointmentId, clinicId, status) {
|
|
8633
|
+
const appointmentRef = doc19(this.db, CALENDAR_COLLECTION, appointmentId);
|
|
8634
|
+
const appointmentDoc = await getDoc22(appointmentRef);
|
|
8635
|
+
if (!appointmentDoc.exists()) {
|
|
8636
|
+
throw new Error(`Appointment with ID ${appointmentId} not found`);
|
|
8637
|
+
}
|
|
8638
|
+
const appointment = appointmentDoc.data();
|
|
8639
|
+
if (appointment.clinicBranchId !== clinicId) {
|
|
8640
|
+
throw new Error("Appointment does not belong to the specified clinic");
|
|
8641
|
+
}
|
|
8642
|
+
this.validateStatusTransition(appointment.status, status);
|
|
8643
|
+
const updateParams = {
|
|
8644
|
+
appointmentId,
|
|
8645
|
+
clinicId,
|
|
8646
|
+
doctorId: appointment.practitionerProfileId || "",
|
|
8647
|
+
patientId: appointment.patientProfileId || "",
|
|
8648
|
+
status
|
|
8649
|
+
};
|
|
8650
|
+
await this.validateUpdatePermissions(updateParams);
|
|
8651
|
+
return this.updateAppointment(updateParams);
|
|
8652
|
+
}
|
|
8653
|
+
/**
|
|
8654
|
+
* Validates status transition
|
|
8655
|
+
* @param currentStatus - Current status
|
|
8656
|
+
* @param newStatus - New status
|
|
8657
|
+
* @throws Error if transition is invalid
|
|
8658
|
+
*/
|
|
8659
|
+
validateStatusTransition(currentStatus, newStatus) {
|
|
8660
|
+
const validTransitions = {
|
|
8661
|
+
["pending" /* PENDING */]: [
|
|
8662
|
+
"confirmed" /* CONFIRMED */,
|
|
8663
|
+
"rejected" /* REJECTED */,
|
|
8664
|
+
"canceled" /* CANCELED */
|
|
8665
|
+
],
|
|
8666
|
+
["confirmed" /* CONFIRMED */]: [
|
|
8667
|
+
"canceled" /* CANCELED */,
|
|
8668
|
+
"completed" /* COMPLETED */,
|
|
8669
|
+
"rescheduled" /* RESCHEDULED */
|
|
8670
|
+
],
|
|
8671
|
+
["rejected" /* REJECTED */]: [],
|
|
8672
|
+
["canceled" /* CANCELED */]: [],
|
|
8673
|
+
["rescheduled" /* RESCHEDULED */]: [
|
|
8674
|
+
"confirmed" /* CONFIRMED */,
|
|
8675
|
+
"canceled" /* CANCELED */
|
|
8676
|
+
],
|
|
8677
|
+
["completed" /* COMPLETED */]: []
|
|
8678
|
+
};
|
|
8679
|
+
if (!validTransitions[currentStatus].includes(newStatus)) {
|
|
8680
|
+
throw new Error(
|
|
8681
|
+
`Invalid status transition from ${currentStatus} to ${newStatus}`
|
|
8682
|
+
);
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
8685
|
+
/**
|
|
8686
|
+
* Syncs appointment with external calendars based on entity type and status
|
|
8687
|
+
* @param appointment - Calendar event to sync
|
|
8688
|
+
*/
|
|
8689
|
+
async syncAppointmentWithExternalCalendars(appointment) {
|
|
8690
|
+
if (!appointment.practitionerProfileId || !appointment.patientProfileId) {
|
|
8691
|
+
return;
|
|
8692
|
+
}
|
|
8693
|
+
try {
|
|
8694
|
+
const [doctorCalendars, patientCalendars] = await Promise.all([
|
|
8695
|
+
this.syncedCalendarsService.getPractitionerSyncedCalendars(
|
|
8696
|
+
appointment.practitionerProfileId
|
|
8697
|
+
),
|
|
8698
|
+
this.syncedCalendarsService.getPatientSyncedCalendars(
|
|
8699
|
+
appointment.patientProfileId
|
|
8700
|
+
)
|
|
8701
|
+
]);
|
|
8702
|
+
const activeDoctorCalendars = doctorCalendars.filter(
|
|
8703
|
+
(cal) => cal.isActive
|
|
8704
|
+
);
|
|
8705
|
+
const activePatientCalendars = patientCalendars.filter(
|
|
8706
|
+
(cal) => cal.isActive
|
|
8707
|
+
);
|
|
8708
|
+
if (activeDoctorCalendars.length === 0 && activePatientCalendars.length === 0) {
|
|
8709
|
+
return;
|
|
8710
|
+
}
|
|
8711
|
+
if (appointment.syncStatus !== "internal" /* INTERNAL */) {
|
|
8712
|
+
return;
|
|
8713
|
+
}
|
|
8714
|
+
if (appointment.status === "confirmed" /* CONFIRMED */ && activeDoctorCalendars.length > 0) {
|
|
8715
|
+
await Promise.all(
|
|
8716
|
+
activeDoctorCalendars.map(
|
|
8717
|
+
(calendar) => this.syncEventToExternalCalendar(appointment, calendar, "doctor")
|
|
8718
|
+
)
|
|
8719
|
+
);
|
|
8720
|
+
}
|
|
8721
|
+
if (appointment.status !== "canceled" /* CANCELED */ && appointment.status !== "rejected" /* REJECTED */ && activePatientCalendars.length > 0) {
|
|
8722
|
+
await Promise.all(
|
|
8723
|
+
activePatientCalendars.map(
|
|
8724
|
+
(calendar) => this.syncEventToExternalCalendar(appointment, calendar, "patient")
|
|
8725
|
+
)
|
|
8726
|
+
);
|
|
8727
|
+
}
|
|
8728
|
+
} catch (error) {
|
|
8729
|
+
console.error("Error syncing with external calendars:", error);
|
|
8730
|
+
}
|
|
8731
|
+
}
|
|
8732
|
+
/**
|
|
8733
|
+
* Syncs a single event to an external calendar
|
|
8734
|
+
* @param appointment - Calendar event to sync
|
|
8735
|
+
* @param calendar - External calendar to sync with
|
|
8736
|
+
* @param entityType - Type of entity owning the calendar
|
|
8737
|
+
*/
|
|
8738
|
+
async syncEventToExternalCalendar(appointment, calendar, entityType) {
|
|
8739
|
+
var _a, _b, _c, _d, _e;
|
|
8740
|
+
try {
|
|
8741
|
+
const eventToSync = { ...appointment };
|
|
8742
|
+
let eventTitle = appointment.eventName;
|
|
8743
|
+
const clinicName = ((_a = appointment.clinicBranchInfo) == null ? void 0 : _a.name) || "Clinic";
|
|
8744
|
+
if (entityType === "patient") {
|
|
8745
|
+
eventTitle = `[${appointment.status}] ${eventTitle} @ ${clinicName}`;
|
|
8746
|
+
} else {
|
|
8747
|
+
eventTitle = `${eventTitle} - Patient: ${((_b = appointment.patientProfileInfo) == null ? void 0 : _b.fullName) || "Unknown"} @ ${clinicName}`;
|
|
8748
|
+
}
|
|
8749
|
+
eventToSync.eventName = eventTitle;
|
|
8750
|
+
const existingSyncId = (_d = (_c = appointment.syncedCalendarEventId) == null ? void 0 : _c.find(
|
|
8751
|
+
(sync) => sync.syncedCalendarProvider === calendar.provider
|
|
8752
|
+
)) == null ? void 0 : _d.eventId;
|
|
8753
|
+
if (calendar.provider === "google" /* GOOGLE */) {
|
|
8754
|
+
const result = await this.syncedCalendarsService.syncPractitionerEventsToGoogleCalendar(
|
|
8755
|
+
entityType === "doctor" ? appointment.practitionerProfileId : appointment.patientProfileId,
|
|
8756
|
+
calendar.id,
|
|
8757
|
+
[eventToSync],
|
|
8758
|
+
existingSyncId
|
|
8759
|
+
// Pass existing sync ID if we have one
|
|
8760
|
+
);
|
|
8761
|
+
if (result.success && ((_e = result.eventIds) == null ? void 0 : _e.length) && !existingSyncId) {
|
|
8762
|
+
const newSyncEvent = {
|
|
8763
|
+
eventId: result.eventIds[0],
|
|
8764
|
+
syncedCalendarProvider: calendar.provider,
|
|
8765
|
+
syncedAt: Timestamp23.now()
|
|
8766
|
+
};
|
|
8767
|
+
await this.updateEventWithSyncId(
|
|
8768
|
+
entityType === "doctor" ? appointment.practitionerProfileId : appointment.patientProfileId,
|
|
8769
|
+
entityType,
|
|
8770
|
+
appointment.id,
|
|
8771
|
+
newSyncEvent
|
|
8772
|
+
);
|
|
8773
|
+
}
|
|
8774
|
+
}
|
|
8775
|
+
} catch (error) {
|
|
8776
|
+
console.error(`Error syncing with ${entityType}'s calendar:`, error);
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8779
|
+
/**
|
|
8780
|
+
* Updates an event with a new sync ID
|
|
8781
|
+
* @param entityId - ID of the entity (doctor or patient)
|
|
8782
|
+
* @param entityType - Type of entity
|
|
8783
|
+
* @param eventId - ID of the event
|
|
8784
|
+
* @param syncEvent - Sync event information
|
|
8785
|
+
*/
|
|
8786
|
+
async updateEventWithSyncId(entityId, entityType, eventId, syncEvent) {
|
|
8787
|
+
try {
|
|
8788
|
+
const collectionPath = entityType === "doctor" ? `${PRACTITIONERS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}` : `${PATIENTS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
|
|
8789
|
+
const eventRef = doc19(this.db, collectionPath, eventId);
|
|
8790
|
+
const eventDoc = await getDoc22(eventRef);
|
|
8791
|
+
if (eventDoc.exists()) {
|
|
8792
|
+
const event = eventDoc.data();
|
|
8793
|
+
const syncIds = [...event.syncedCalendarEventId || []];
|
|
8794
|
+
const existingSyncIndex = syncIds.findIndex(
|
|
8795
|
+
(sync) => sync.syncedCalendarProvider === syncEvent.syncedCalendarProvider
|
|
8796
|
+
);
|
|
8797
|
+
if (existingSyncIndex >= 0) {
|
|
8798
|
+
syncIds[existingSyncIndex] = syncEvent;
|
|
8799
|
+
} else {
|
|
8800
|
+
syncIds.push(syncEvent);
|
|
8801
|
+
}
|
|
8802
|
+
await updateDoc20(eventRef, {
|
|
8803
|
+
syncedCalendarEventId: syncIds,
|
|
8804
|
+
updatedAt: serverTimestamp18()
|
|
8805
|
+
});
|
|
8806
|
+
console.log(
|
|
8807
|
+
`Updated event ${eventId} with sync ID ${syncEvent.eventId}`
|
|
8808
|
+
);
|
|
8809
|
+
}
|
|
8810
|
+
} catch (error) {
|
|
8811
|
+
console.error("Error updating event with sync ID:", error);
|
|
8812
|
+
}
|
|
8813
|
+
}
|
|
8814
|
+
/**
|
|
8815
|
+
* Validates update permissions and parameters
|
|
8816
|
+
* @param params - Update parameters to validate
|
|
8817
|
+
*/
|
|
8818
|
+
async validateUpdatePermissions(params) {
|
|
8819
|
+
await updateAppointmentSchema.parseAsync(params);
|
|
8820
|
+
}
|
|
8821
|
+
/**
|
|
8822
|
+
* Gets clinic working hours for a specific date
|
|
8823
|
+
* @param clinicId - ID of the clinic
|
|
8824
|
+
* @param date - Date to get working hours for
|
|
8825
|
+
* @returns Working hours for the clinic
|
|
8826
|
+
*/
|
|
8827
|
+
async getClinicWorkingHours(clinicId, date) {
|
|
8828
|
+
const clinicRef = doc19(this.db, CLINICS_COLLECTION, clinicId);
|
|
8829
|
+
const clinicDoc = await getDoc22(clinicRef);
|
|
8830
|
+
if (!clinicDoc.exists()) {
|
|
8831
|
+
throw new Error(`Clinic with ID ${clinicId} not found`);
|
|
8832
|
+
}
|
|
8833
|
+
const workingHours = [];
|
|
8834
|
+
const dayOfWeek = date.getDay();
|
|
8835
|
+
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
|
8836
|
+
return workingHours;
|
|
8837
|
+
}
|
|
8838
|
+
const workingDate = new Date(date);
|
|
8839
|
+
workingDate.setHours(9, 0, 0, 0);
|
|
8840
|
+
const startTime = new Date(workingDate);
|
|
8841
|
+
workingDate.setHours(17, 0, 0, 0);
|
|
8842
|
+
const endTime = new Date(workingDate);
|
|
8843
|
+
workingHours.push({
|
|
8844
|
+
start: startTime,
|
|
8845
|
+
end: endTime,
|
|
8846
|
+
isAvailable: true
|
|
8847
|
+
});
|
|
8848
|
+
return workingHours;
|
|
8849
|
+
}
|
|
8850
|
+
/**
|
|
8851
|
+
* Gets doctor's schedule for a specific date
|
|
8852
|
+
* @param doctorId - ID of the doctor
|
|
8853
|
+
* @param date - Date to get schedule for
|
|
8854
|
+
* @returns Doctor's schedule
|
|
8855
|
+
*/
|
|
8856
|
+
async getDoctorSchedule(doctorId, date) {
|
|
8857
|
+
const practitionerRef = doc19(this.db, PRACTITIONERS_COLLECTION, doctorId);
|
|
8858
|
+
const practitionerDoc = await getDoc22(practitionerRef);
|
|
8859
|
+
if (!practitionerDoc.exists()) {
|
|
8860
|
+
throw new Error(`Doctor with ID ${doctorId} not found`);
|
|
8861
|
+
}
|
|
8862
|
+
const schedule = [];
|
|
8863
|
+
const dayOfWeek = date.getDay();
|
|
8864
|
+
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
|
8865
|
+
return schedule;
|
|
8866
|
+
}
|
|
8867
|
+
const scheduleDate = new Date(date);
|
|
8868
|
+
scheduleDate.setHours(9, 0, 0, 0);
|
|
8869
|
+
const startTime = new Date(scheduleDate);
|
|
8870
|
+
scheduleDate.setHours(17, 0, 0, 0);
|
|
8871
|
+
const endTime = new Date(scheduleDate);
|
|
8872
|
+
schedule.push({
|
|
8873
|
+
start: startTime,
|
|
8874
|
+
end: endTime,
|
|
8875
|
+
isAvailable: true
|
|
8876
|
+
});
|
|
8877
|
+
return schedule;
|
|
8878
|
+
}
|
|
8879
|
+
/**
|
|
8880
|
+
* Gets doctor's appointments for a specific date
|
|
8881
|
+
* @param doctorId - ID of the doctor
|
|
8882
|
+
* @param date - Date to get appointments for
|
|
8883
|
+
* @returns Array of calendar events
|
|
8884
|
+
*/
|
|
8885
|
+
async getDoctorAppointments(doctorId, date) {
|
|
8886
|
+
const startOfDay = new Date(date);
|
|
8887
|
+
startOfDay.setHours(0, 0, 0, 0);
|
|
8888
|
+
const endOfDay = new Date(date);
|
|
8889
|
+
endOfDay.setHours(23, 59, 59, 999);
|
|
8890
|
+
const appointmentsRef = collection17(this.db, CALENDAR_COLLECTION);
|
|
8891
|
+
const q = query16(
|
|
8892
|
+
appointmentsRef,
|
|
8893
|
+
where16("practitionerProfileId", "==", doctorId),
|
|
8894
|
+
where16("eventTime.start", ">=", Timestamp23.fromDate(startOfDay)),
|
|
8895
|
+
where16("eventTime.start", "<=", Timestamp23.fromDate(endOfDay)),
|
|
8896
|
+
where16("status", "in", [
|
|
8897
|
+
"confirmed" /* CONFIRMED */,
|
|
8898
|
+
"pending" /* PENDING */
|
|
8899
|
+
])
|
|
8900
|
+
);
|
|
8901
|
+
const querySnapshot = await getDocs16(q);
|
|
8902
|
+
return querySnapshot.docs.map((doc20) => doc20.data());
|
|
8903
|
+
}
|
|
8904
|
+
/**
|
|
8905
|
+
* Calculates available time slots based on working hours, schedule and existing appointments
|
|
8906
|
+
* @param workingHours - Clinic working hours
|
|
8907
|
+
* @param doctorSchedule - Doctor's schedule
|
|
8908
|
+
* @param existingAppointments - Existing appointments
|
|
8909
|
+
* @returns Array of available time slots
|
|
8910
|
+
*/
|
|
8911
|
+
calculateAvailableSlots(workingHours, doctorSchedule, existingAppointments) {
|
|
8912
|
+
const availableSlots = [];
|
|
8913
|
+
for (const workingHour of workingHours) {
|
|
8914
|
+
for (const scheduleSlot of doctorSchedule) {
|
|
8915
|
+
const overlapStart = new Date(
|
|
8916
|
+
Math.max(workingHour.start.getTime(), scheduleSlot.start.getTime())
|
|
8917
|
+
);
|
|
8918
|
+
const overlapEnd = new Date(
|
|
8919
|
+
Math.min(workingHour.end.getTime(), scheduleSlot.end.getTime())
|
|
8920
|
+
);
|
|
8921
|
+
if (overlapStart < overlapEnd && workingHour.isAvailable && scheduleSlot.isAvailable) {
|
|
8922
|
+
let slotStart = new Date(overlapStart);
|
|
8923
|
+
while (slotStart < overlapEnd) {
|
|
8924
|
+
const slotEnd = new Date(
|
|
8925
|
+
slotStart.getTime() + MIN_APPOINTMENT_DURATION2 * 60 * 1e3
|
|
8926
|
+
);
|
|
8927
|
+
const hasOverlap = existingAppointments.some((appointment) => {
|
|
8928
|
+
const appointmentStart = appointment.eventTime.start.toDate();
|
|
8929
|
+
const appointmentEnd = appointment.eventTime.end.toDate();
|
|
8930
|
+
return slotStart >= appointmentStart && slotStart < appointmentEnd || slotEnd > appointmentStart && slotEnd <= appointmentEnd;
|
|
8931
|
+
});
|
|
8932
|
+
if (!hasOverlap && slotEnd <= overlapEnd) {
|
|
8933
|
+
availableSlots.push({
|
|
8934
|
+
start: new Date(slotStart),
|
|
8935
|
+
end: new Date(slotEnd),
|
|
8936
|
+
isAvailable: true
|
|
8937
|
+
});
|
|
8938
|
+
}
|
|
8939
|
+
slotStart = new Date(
|
|
8940
|
+
slotStart.getTime() + MIN_APPOINTMENT_DURATION2 * 60 * 1e3
|
|
8941
|
+
);
|
|
8942
|
+
}
|
|
8943
|
+
}
|
|
8944
|
+
}
|
|
8945
|
+
}
|
|
8946
|
+
return availableSlots;
|
|
8947
|
+
}
|
|
8948
|
+
/**
|
|
8949
|
+
* Fetches and creates info cards for clinic, doctor, and patient profiles
|
|
8950
|
+
* @param clinicId - ID of the clinic
|
|
8951
|
+
* @param doctorId - ID of the doctor
|
|
8952
|
+
* @param patientId - ID of the patient
|
|
8953
|
+
* @returns Object containing info cards for all profiles
|
|
8954
|
+
*/
|
|
8955
|
+
async fetchProfileInfoCards(clinicId, doctorId, patientId) {
|
|
8956
|
+
var _a;
|
|
8957
|
+
try {
|
|
8958
|
+
const [clinicDoc, practitionerDoc, patientDoc, patientSensitiveInfoDoc] = await Promise.all([
|
|
8959
|
+
getDoc22(doc19(this.db, CLINICS_COLLECTION, clinicId)),
|
|
8960
|
+
getDoc22(doc19(this.db, PRACTITIONERS_COLLECTION, doctorId)),
|
|
8961
|
+
getDoc22(doc19(this.db, PATIENTS_COLLECTION, patientId)),
|
|
8962
|
+
getDoc22(
|
|
8963
|
+
doc19(
|
|
8964
|
+
this.db,
|
|
8965
|
+
PATIENTS_COLLECTION,
|
|
8966
|
+
patientId,
|
|
8967
|
+
PATIENT_SENSITIVE_INFO_COLLECTION,
|
|
8968
|
+
patientId
|
|
8969
|
+
)
|
|
8970
|
+
)
|
|
8971
|
+
]);
|
|
8972
|
+
const clinicInfo = clinicDoc.exists() ? {
|
|
8973
|
+
id: clinicDoc.id,
|
|
8974
|
+
featuredPhoto: clinicDoc.data().featuredPhoto || "",
|
|
8975
|
+
name: clinicDoc.data().name,
|
|
8976
|
+
description: clinicDoc.data().description || "",
|
|
8977
|
+
location: clinicDoc.data().location,
|
|
8978
|
+
contactInfo: clinicDoc.data().contactInfo
|
|
8979
|
+
} : null;
|
|
8980
|
+
const practitionerInfo = practitionerDoc.exists() ? {
|
|
8981
|
+
id: practitionerDoc.id,
|
|
8982
|
+
practitionerPhoto: practitionerDoc.data().basicInfo.profileImageUrl || null,
|
|
8983
|
+
name: `${practitionerDoc.data().basicInfo.firstName} ${practitionerDoc.data().basicInfo.lastName}`,
|
|
8984
|
+
email: practitionerDoc.data().basicInfo.email,
|
|
8985
|
+
phone: practitionerDoc.data().basicInfo.phoneNumber || null,
|
|
8986
|
+
certification: practitionerDoc.data().certification
|
|
8987
|
+
} : null;
|
|
8988
|
+
let patientInfo = null;
|
|
8989
|
+
if (patientSensitiveInfoDoc.exists()) {
|
|
8990
|
+
const sensitiveData = patientSensitiveInfoDoc.data();
|
|
8991
|
+
patientInfo = {
|
|
8992
|
+
id: patientId,
|
|
8993
|
+
fullName: `${sensitiveData.firstName} ${sensitiveData.lastName}`,
|
|
8994
|
+
email: sensitiveData.email || "",
|
|
8995
|
+
phone: sensitiveData.phoneNumber || null,
|
|
8996
|
+
dateOfBirth: sensitiveData.dateOfBirth || Timestamp23.now(),
|
|
8997
|
+
gender: sensitiveData.gender || "other" /* OTHER */
|
|
8998
|
+
};
|
|
8999
|
+
} else if (patientDoc.exists()) {
|
|
9000
|
+
patientInfo = {
|
|
9001
|
+
id: patientDoc.id,
|
|
9002
|
+
fullName: patientDoc.data().displayName,
|
|
9003
|
+
email: ((_a = patientDoc.data().contactInfo) == null ? void 0 : _a.email) || "",
|
|
9004
|
+
phone: patientDoc.data().phoneNumber || null,
|
|
9005
|
+
dateOfBirth: patientDoc.data().dateOfBirth || Timestamp23.now(),
|
|
9006
|
+
gender: patientDoc.data().gender || "other" /* OTHER */
|
|
9007
|
+
};
|
|
9008
|
+
}
|
|
9009
|
+
return {
|
|
9010
|
+
clinicInfo,
|
|
9011
|
+
practitionerInfo,
|
|
9012
|
+
patientInfo
|
|
9013
|
+
};
|
|
9014
|
+
} catch (error) {
|
|
9015
|
+
console.error("Error fetching profile info cards:", error);
|
|
9016
|
+
return {
|
|
9017
|
+
clinicInfo: null,
|
|
9018
|
+
practitionerInfo: null,
|
|
9019
|
+
patientInfo: null
|
|
9020
|
+
};
|
|
9021
|
+
}
|
|
9022
|
+
}
|
|
9023
|
+
// #endregion
|
|
9024
|
+
};
|
|
9025
|
+
|
|
9026
|
+
// src/validations/notification.schema.ts
|
|
9027
|
+
import { z as z18 } from "zod";
|
|
9028
|
+
var baseNotificationSchema = z18.object({
|
|
9029
|
+
id: z18.string().optional(),
|
|
9030
|
+
userId: z18.string(),
|
|
9031
|
+
notificationTime: z18.any(),
|
|
6390
9032
|
// Timestamp
|
|
6391
|
-
notificationType:
|
|
6392
|
-
notificationTokens:
|
|
6393
|
-
status:
|
|
6394
|
-
createdAt:
|
|
9033
|
+
notificationType: z18.nativeEnum(NotificationType),
|
|
9034
|
+
notificationTokens: z18.array(z18.string()),
|
|
9035
|
+
status: z18.nativeEnum(NotificationStatus),
|
|
9036
|
+
createdAt: z18.any().optional(),
|
|
6395
9037
|
// Timestamp
|
|
6396
|
-
updatedAt:
|
|
9038
|
+
updatedAt: z18.any().optional(),
|
|
6397
9039
|
// Timestamp
|
|
6398
|
-
title:
|
|
6399
|
-
body:
|
|
6400
|
-
isRead:
|
|
6401
|
-
userRole:
|
|
9040
|
+
title: z18.string(),
|
|
9041
|
+
body: z18.string(),
|
|
9042
|
+
isRead: z18.boolean(),
|
|
9043
|
+
userRole: z18.nativeEnum(UserRole)
|
|
6402
9044
|
});
|
|
6403
9045
|
var preRequirementNotificationSchema = baseNotificationSchema.extend({
|
|
6404
|
-
notificationType:
|
|
6405
|
-
treatmentId:
|
|
6406
|
-
requirements:
|
|
6407
|
-
deadline:
|
|
9046
|
+
notificationType: z18.literal("preRequirement" /* PRE_REQUIREMENT */),
|
|
9047
|
+
treatmentId: z18.string(),
|
|
9048
|
+
requirements: z18.array(z18.string()),
|
|
9049
|
+
deadline: z18.any()
|
|
6408
9050
|
// Timestamp
|
|
6409
9051
|
});
|
|
6410
9052
|
var postRequirementNotificationSchema = baseNotificationSchema.extend({
|
|
6411
|
-
notificationType:
|
|
6412
|
-
treatmentId:
|
|
6413
|
-
requirements:
|
|
6414
|
-
deadline:
|
|
9053
|
+
notificationType: z18.literal("postRequirement" /* POST_REQUIREMENT */),
|
|
9054
|
+
treatmentId: z18.string(),
|
|
9055
|
+
requirements: z18.array(z18.string()),
|
|
9056
|
+
deadline: z18.any()
|
|
6415
9057
|
// Timestamp
|
|
6416
9058
|
});
|
|
6417
9059
|
var appointmentReminderNotificationSchema = baseNotificationSchema.extend({
|
|
6418
|
-
notificationType:
|
|
6419
|
-
appointmentId:
|
|
6420
|
-
appointmentTime:
|
|
9060
|
+
notificationType: z18.literal("appointmentReminder" /* APPOINTMENT_REMINDER */),
|
|
9061
|
+
appointmentId: z18.string(),
|
|
9062
|
+
appointmentTime: z18.any(),
|
|
6421
9063
|
// Timestamp
|
|
6422
|
-
treatmentType:
|
|
6423
|
-
doctorName:
|
|
9064
|
+
treatmentType: z18.string(),
|
|
9065
|
+
doctorName: z18.string()
|
|
6424
9066
|
});
|
|
6425
9067
|
var appointmentNotificationSchema = baseNotificationSchema.extend({
|
|
6426
|
-
notificationType:
|
|
6427
|
-
appointmentId:
|
|
6428
|
-
appointmentStatus:
|
|
6429
|
-
previousStatus:
|
|
6430
|
-
reason:
|
|
9068
|
+
notificationType: z18.literal("appointmentNotification" /* APPOINTMENT_NOTIFICATION */),
|
|
9069
|
+
appointmentId: z18.string(),
|
|
9070
|
+
appointmentStatus: z18.string(),
|
|
9071
|
+
previousStatus: z18.string(),
|
|
9072
|
+
reason: z18.string().optional()
|
|
6431
9073
|
});
|
|
6432
|
-
var notificationSchema =
|
|
9074
|
+
var notificationSchema = z18.discriminatedUnion("notificationType", [
|
|
6433
9075
|
preRequirementNotificationSchema,
|
|
6434
9076
|
postRequirementNotificationSchema,
|
|
6435
9077
|
appointmentReminderNotificationSchema,
|
|
@@ -6441,9 +9083,14 @@ export {
|
|
|
6441
9083
|
AllergyType,
|
|
6442
9084
|
AuthService,
|
|
6443
9085
|
BlockingCondition,
|
|
9086
|
+
CALENDAR_COLLECTION,
|
|
6444
9087
|
CLINICS_COLLECTION,
|
|
6445
9088
|
CLINIC_ADMINS_COLLECTION,
|
|
6446
9089
|
CLINIC_GROUPS_COLLECTION,
|
|
9090
|
+
CalendarEventStatus,
|
|
9091
|
+
CalendarEventType,
|
|
9092
|
+
CalendarServiceV2,
|
|
9093
|
+
CalendarSyncStatus,
|
|
6447
9094
|
CertificationLevel,
|
|
6448
9095
|
CertificationSpecialty,
|
|
6449
9096
|
ClinicAdminService,
|
|
@@ -6484,7 +9131,10 @@ export {
|
|
|
6484
9131
|
PractitionerService,
|
|
6485
9132
|
PricingMeasure,
|
|
6486
9133
|
ProcedureFamily,
|
|
9134
|
+
SYNCED_CALENDARS_COLLECTION,
|
|
6487
9135
|
SubscriptionModel,
|
|
9136
|
+
SyncedCalendarProvider,
|
|
9137
|
+
SyncedCalendarsService,
|
|
6488
9138
|
TreatmentBenefit,
|
|
6489
9139
|
USER_ERRORS,
|
|
6490
9140
|
UserService,
|
|
@@ -6501,6 +9151,8 @@ export {
|
|
|
6501
9151
|
appointmentReminderNotificationSchema,
|
|
6502
9152
|
baseNotificationSchema,
|
|
6503
9153
|
blockingConditionSchema,
|
|
9154
|
+
calendarEventSchema,
|
|
9155
|
+
calendarEventTimeSchema,
|
|
6504
9156
|
clinicAdminOptionsSchema,
|
|
6505
9157
|
clinicAdminSchema,
|
|
6506
9158
|
clinicAdminSignupSchema,
|
|
@@ -6516,6 +9168,8 @@ export {
|
|
|
6516
9168
|
contactPersonSchema,
|
|
6517
9169
|
contraindicationSchema,
|
|
6518
9170
|
createAdminTokenSchema,
|
|
9171
|
+
createAppointmentSchema,
|
|
9172
|
+
createCalendarEventSchema,
|
|
6519
9173
|
createClinicAdminSchema,
|
|
6520
9174
|
createClinicGroupSchema,
|
|
6521
9175
|
createClinicSchema,
|
|
@@ -6547,21 +9201,30 @@ export {
|
|
|
6547
9201
|
patientDoctorSchema,
|
|
6548
9202
|
patientLocationInfoSchema,
|
|
6549
9203
|
patientMedicalInfoSchema,
|
|
9204
|
+
patientProfileInfoSchema,
|
|
6550
9205
|
patientProfileSchema,
|
|
6551
9206
|
patientSensitiveInfoSchema,
|
|
6552
9207
|
postRequirementNotificationSchema,
|
|
6553
9208
|
practitionerBasicInfoSchema,
|
|
6554
9209
|
practitionerCertificationSchema,
|
|
6555
9210
|
practitionerClinicProceduresSchema,
|
|
9211
|
+
practitionerClinicWorkingHoursSchema,
|
|
9212
|
+
practitionerProfileInfoSchema,
|
|
6556
9213
|
practitionerReviewSchema,
|
|
6557
9214
|
practitionerSchema,
|
|
6558
9215
|
practitionerWorkingHoursSchema,
|
|
6559
9216
|
preRequirementNotificationSchema,
|
|
9217
|
+
procedureCategorizationSchema,
|
|
9218
|
+
procedureInfoSchema,
|
|
6560
9219
|
reviewInfoSchema,
|
|
6561
9220
|
serviceInfoSchema,
|
|
9221
|
+
syncedCalendarEventSchema,
|
|
9222
|
+
timeSlotSchema2 as timeSlotSchema,
|
|
6562
9223
|
timestampSchema,
|
|
6563
9224
|
updateAllergySchema,
|
|
9225
|
+
updateAppointmentSchema,
|
|
6564
9226
|
updateBlockingConditionSchema,
|
|
9227
|
+
updateCalendarEventSchema,
|
|
6565
9228
|
updateClinicAdminSchema,
|
|
6566
9229
|
updateClinicGroupSchema,
|
|
6567
9230
|
updateClinicSchema,
|