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