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