@blackcode_sa/metaestetics-api 1.8.1 → 1.8.3
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/backoffice/index.d.mts +7 -7
- package/dist/backoffice/index.d.ts +7 -7
- package/dist/backoffice/index.js +24 -24
- package/dist/backoffice/index.mjs +18 -18
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2968 -526
- package/dist/index.mjs +3064 -567
- package/package.json +1 -1
- package/src/backoffice/services/documentation-template.service.ts +1 -1
- package/src/backoffice/validations/schemas.ts +12 -11
- package/src/index.backup.ts +1 -1
- package/src/services/appointment/appointment.service.ts +1 -1
- package/src/services/calendar/index.ts +1 -1
- package/src/services/index.ts +1 -0
- package/src/validations/patient/patient-requirements.schema.ts +2 -2
- /package/src/services/calendar/{calendar-refactored.service.ts → calendar.v2.service.ts} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -593,20 +593,20 @@ import {
|
|
|
593
593
|
} from "firebase/firestore";
|
|
594
594
|
|
|
595
595
|
// src/types/calendar/index.ts
|
|
596
|
-
var CalendarEventStatus = /* @__PURE__ */ ((
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
return
|
|
596
|
+
var CalendarEventStatus = /* @__PURE__ */ ((CalendarEventStatus4) => {
|
|
597
|
+
CalendarEventStatus4["PENDING"] = "pending";
|
|
598
|
+
CalendarEventStatus4["CONFIRMED"] = "confirmed";
|
|
599
|
+
CalendarEventStatus4["REJECTED"] = "rejected";
|
|
600
|
+
CalendarEventStatus4["CANCELED"] = "canceled";
|
|
601
|
+
CalendarEventStatus4["RESCHEDULED"] = "rescheduled";
|
|
602
|
+
CalendarEventStatus4["COMPLETED"] = "completed";
|
|
603
|
+
CalendarEventStatus4["NO_SHOW"] = "no_show";
|
|
604
|
+
return CalendarEventStatus4;
|
|
605
605
|
})(CalendarEventStatus || {});
|
|
606
|
-
var CalendarSyncStatus = /* @__PURE__ */ ((
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
return
|
|
606
|
+
var CalendarSyncStatus = /* @__PURE__ */ ((CalendarSyncStatus4) => {
|
|
607
|
+
CalendarSyncStatus4["INTERNAL"] = "internal";
|
|
608
|
+
CalendarSyncStatus4["EXTERNAL"] = "external";
|
|
609
|
+
return CalendarSyncStatus4;
|
|
610
610
|
})(CalendarSyncStatus || {});
|
|
611
611
|
var CalendarEventType = /* @__PURE__ */ ((CalendarEventType3) => {
|
|
612
612
|
CalendarEventType3["APPOINTMENT"] = "appointment";
|
|
@@ -978,7 +978,7 @@ async function searchAppointmentsUtil(db, params) {
|
|
|
978
978
|
const q = query(collection(db, APPOINTMENTS_COLLECTION), ...constraints);
|
|
979
979
|
const querySnapshot = await getDocs(q);
|
|
980
980
|
const appointments = querySnapshot.docs.map(
|
|
981
|
-
(
|
|
981
|
+
(doc32) => doc32.data()
|
|
982
982
|
);
|
|
983
983
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
984
984
|
return { appointments, lastDoc };
|
|
@@ -1785,7 +1785,7 @@ var AppointmentService = class extends BaseService {
|
|
|
1785
1785
|
);
|
|
1786
1786
|
const querySnapshot = await getDocs2(q);
|
|
1787
1787
|
const appointments = querySnapshot.docs.map(
|
|
1788
|
-
(
|
|
1788
|
+
(doc32) => doc32.data()
|
|
1789
1789
|
);
|
|
1790
1790
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
1791
1791
|
console.log(
|
|
@@ -1858,7 +1858,7 @@ var AppointmentService = class extends BaseService {
|
|
|
1858
1858
|
);
|
|
1859
1859
|
const querySnapshot = await getDocs2(q);
|
|
1860
1860
|
const appointments = querySnapshot.docs.map(
|
|
1861
|
-
(
|
|
1861
|
+
(doc32) => doc32.data()
|
|
1862
1862
|
);
|
|
1863
1863
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
1864
1864
|
console.log(
|
|
@@ -2589,11 +2589,11 @@ var UserRole = /* @__PURE__ */ ((UserRole2) => {
|
|
|
2589
2589
|
var USERS_COLLECTION = "users";
|
|
2590
2590
|
|
|
2591
2591
|
// src/types/calendar/synced-calendar.types.ts
|
|
2592
|
-
var SyncedCalendarProvider = /* @__PURE__ */ ((
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
return
|
|
2592
|
+
var SyncedCalendarProvider = /* @__PURE__ */ ((SyncedCalendarProvider3) => {
|
|
2593
|
+
SyncedCalendarProvider3["GOOGLE"] = "google";
|
|
2594
|
+
SyncedCalendarProvider3["OUTLOOK"] = "outlook";
|
|
2595
|
+
SyncedCalendarProvider3["APPLE"] = "apple";
|
|
2596
|
+
return SyncedCalendarProvider3;
|
|
2597
2597
|
})(SyncedCalendarProvider || {});
|
|
2598
2598
|
var SYNCED_CALENDARS_COLLECTION = "syncedCalendars";
|
|
2599
2599
|
|
|
@@ -3167,7 +3167,7 @@ var MediaService = class extends BaseService {
|
|
|
3167
3167
|
try {
|
|
3168
3168
|
const querySnapshot = await getDocs3(finalQuery);
|
|
3169
3169
|
const mediaList = querySnapshot.docs.map(
|
|
3170
|
-
(
|
|
3170
|
+
(doc32) => doc32.data()
|
|
3171
3171
|
);
|
|
3172
3172
|
console.log(`[MediaService] Found ${mediaList.length} media items.`);
|
|
3173
3173
|
return mediaList;
|
|
@@ -3230,8 +3230,8 @@ var getPatientsByClinicUtil = async (db, clinicId, options) => {
|
|
|
3230
3230
|
}
|
|
3231
3231
|
const patientsSnapshot = await getDocs4(q);
|
|
3232
3232
|
const patients = [];
|
|
3233
|
-
patientsSnapshot.forEach((
|
|
3234
|
-
patients.push(
|
|
3233
|
+
patientsSnapshot.forEach((doc32) => {
|
|
3234
|
+
patients.push(doc32.data());
|
|
3235
3235
|
});
|
|
3236
3236
|
console.log(
|
|
3237
3237
|
`[getPatientsByClinicUtil] Found ${patients.length} patients for clinic ID: ${clinicId}`
|
|
@@ -3605,8 +3605,8 @@ var getPatientsByPractitionerUtil = async (db, practitionerId, options) => {
|
|
|
3605
3605
|
}
|
|
3606
3606
|
const patientsSnapshot = await getDocs5(q);
|
|
3607
3607
|
const patients = [];
|
|
3608
|
-
patientsSnapshot.forEach((
|
|
3609
|
-
patients.push(
|
|
3608
|
+
patientsSnapshot.forEach((doc32) => {
|
|
3609
|
+
patients.push(doc32.data());
|
|
3610
3610
|
});
|
|
3611
3611
|
console.log(
|
|
3612
3612
|
`[getPatientsByPractitionerUtil] Found ${patients.length} patients for practitioner ID: ${practitionerId}`
|
|
@@ -4186,7 +4186,7 @@ async function getClinicAdminsByGroup(db, clinicGroupId) {
|
|
|
4186
4186
|
where6("clinicGroupId", "==", clinicGroupId)
|
|
4187
4187
|
);
|
|
4188
4188
|
const querySnapshot = await getDocs6(q);
|
|
4189
|
-
return querySnapshot.docs.map((
|
|
4189
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
4190
4190
|
}
|
|
4191
4191
|
async function updateClinicAdmin(db, adminId, data) {
|
|
4192
4192
|
const admin2 = await getClinicAdmin(db, adminId);
|
|
@@ -4867,9 +4867,9 @@ var updateAllergyUtil = async (db, patientId, data, requesterId, requesterRoles)
|
|
|
4867
4867
|
};
|
|
4868
4868
|
var removeAllergyUtil = async (db, patientId, allergyIndex, requesterId, requesterRoles) => {
|
|
4869
4869
|
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
4870
|
-
const
|
|
4871
|
-
if (!
|
|
4872
|
-
const medicalInfo =
|
|
4870
|
+
const doc32 = await getDoc10(getMedicalInfoDocRef(db, patientId));
|
|
4871
|
+
if (!doc32.exists()) throw new Error("Medical info not found");
|
|
4872
|
+
const medicalInfo = doc32.data();
|
|
4873
4873
|
if (allergyIndex >= medicalInfo.allergies.length) {
|
|
4874
4874
|
throw new Error("Invalid allergy index");
|
|
4875
4875
|
}
|
|
@@ -4896,9 +4896,9 @@ var updateBlockingConditionUtil = async (db, patientId, data, requesterId, reque
|
|
|
4896
4896
|
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
4897
4897
|
const validatedData = updateBlockingConditionSchema.parse(data);
|
|
4898
4898
|
const { conditionIndex, ...updateData } = validatedData;
|
|
4899
|
-
const
|
|
4900
|
-
if (!
|
|
4901
|
-
const medicalInfo =
|
|
4899
|
+
const doc32 = await getDoc10(getMedicalInfoDocRef(db, patientId));
|
|
4900
|
+
if (!doc32.exists()) throw new Error("Medical info not found");
|
|
4901
|
+
const medicalInfo = doc32.data();
|
|
4902
4902
|
if (conditionIndex >= medicalInfo.blockingConditions.length) {
|
|
4903
4903
|
throw new Error("Invalid blocking condition index");
|
|
4904
4904
|
}
|
|
@@ -4915,9 +4915,9 @@ var updateBlockingConditionUtil = async (db, patientId, data, requesterId, reque
|
|
|
4915
4915
|
};
|
|
4916
4916
|
var removeBlockingConditionUtil = async (db, patientId, conditionIndex, requesterId, requesterRoles) => {
|
|
4917
4917
|
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
4918
|
-
const
|
|
4919
|
-
if (!
|
|
4920
|
-
const medicalInfo =
|
|
4918
|
+
const doc32 = await getDoc10(getMedicalInfoDocRef(db, patientId));
|
|
4919
|
+
if (!doc32.exists()) throw new Error("Medical info not found");
|
|
4920
|
+
const medicalInfo = doc32.data();
|
|
4921
4921
|
if (conditionIndex >= medicalInfo.blockingConditions.length) {
|
|
4922
4922
|
throw new Error("Invalid blocking condition index");
|
|
4923
4923
|
}
|
|
@@ -4944,9 +4944,9 @@ var updateContraindicationUtil = async (db, patientId, data, requesterId, reques
|
|
|
4944
4944
|
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
4945
4945
|
const validatedData = updateContraindicationSchema.parse(data);
|
|
4946
4946
|
const { contraindicationIndex, ...updateData } = validatedData;
|
|
4947
|
-
const
|
|
4948
|
-
if (!
|
|
4949
|
-
const medicalInfo =
|
|
4947
|
+
const doc32 = await getDoc10(getMedicalInfoDocRef(db, patientId));
|
|
4948
|
+
if (!doc32.exists()) throw new Error("Medical info not found");
|
|
4949
|
+
const medicalInfo = doc32.data();
|
|
4950
4950
|
if (contraindicationIndex >= medicalInfo.contraindications.length) {
|
|
4951
4951
|
throw new Error("Invalid contraindication index");
|
|
4952
4952
|
}
|
|
@@ -4963,9 +4963,9 @@ var updateContraindicationUtil = async (db, patientId, data, requesterId, reques
|
|
|
4963
4963
|
};
|
|
4964
4964
|
var removeContraindicationUtil = async (db, patientId, contraindicationIndex, requesterId, requesterRoles) => {
|
|
4965
4965
|
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
4966
|
-
const
|
|
4967
|
-
if (!
|
|
4968
|
-
const medicalInfo =
|
|
4966
|
+
const doc32 = await getDoc10(getMedicalInfoDocRef(db, patientId));
|
|
4967
|
+
if (!doc32.exists()) throw new Error("Medical info not found");
|
|
4968
|
+
const medicalInfo = doc32.data();
|
|
4969
4969
|
if (contraindicationIndex >= medicalInfo.contraindications.length) {
|
|
4970
4970
|
throw new Error("Invalid contraindication index");
|
|
4971
4971
|
}
|
|
@@ -4992,9 +4992,9 @@ var updateMedicationUtil = async (db, patientId, data, requesterId, requesterRol
|
|
|
4992
4992
|
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
4993
4993
|
const validatedData = updateMedicationSchema.parse(data);
|
|
4994
4994
|
const { medicationIndex, ...updateData } = validatedData;
|
|
4995
|
-
const
|
|
4996
|
-
if (!
|
|
4997
|
-
const medicalInfo =
|
|
4995
|
+
const doc32 = await getDoc10(getMedicalInfoDocRef(db, patientId));
|
|
4996
|
+
if (!doc32.exists()) throw new Error("Medical info not found");
|
|
4997
|
+
const medicalInfo = doc32.data();
|
|
4998
4998
|
if (medicationIndex >= medicalInfo.currentMedications.length) {
|
|
4999
4999
|
throw new Error("Invalid medication index");
|
|
5000
5000
|
}
|
|
@@ -5011,9 +5011,9 @@ var updateMedicationUtil = async (db, patientId, data, requesterId, requesterRol
|
|
|
5011
5011
|
};
|
|
5012
5012
|
var removeMedicationUtil = async (db, patientId, medicationIndex, requesterId, requesterRoles) => {
|
|
5013
5013
|
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
5014
|
-
const
|
|
5015
|
-
if (!
|
|
5016
|
-
const medicalInfo =
|
|
5014
|
+
const doc32 = await getDoc10(getMedicalInfoDocRef(db, patientId));
|
|
5015
|
+
if (!doc32.exists()) throw new Error("Medical info not found");
|
|
5016
|
+
const medicalInfo = doc32.data();
|
|
5017
5017
|
if (medicationIndex >= medicalInfo.currentMedications.length) {
|
|
5018
5018
|
throw new Error("Invalid medication index");
|
|
5019
5019
|
}
|
|
@@ -5316,7 +5316,7 @@ var searchPatientsUtil = async (db, params, requester) => {
|
|
|
5316
5316
|
const finalQuery = query8(patientsCollectionRef, ...constraints);
|
|
5317
5317
|
const querySnapshot = await getDocs8(finalQuery);
|
|
5318
5318
|
const patients = querySnapshot.docs.map(
|
|
5319
|
-
(
|
|
5319
|
+
(doc32) => doc32.data()
|
|
5320
5320
|
);
|
|
5321
5321
|
console.log(
|
|
5322
5322
|
`[searchPatientsUtil] Found ${patients.length} patients matching criteria.`
|
|
@@ -5348,8 +5348,8 @@ var getAllPatientsUtil = async (db, options) => {
|
|
|
5348
5348
|
}
|
|
5349
5349
|
const patientsSnapshot = await getDocs8(q);
|
|
5350
5350
|
const patients = [];
|
|
5351
|
-
patientsSnapshot.forEach((
|
|
5352
|
-
patients.push(
|
|
5351
|
+
patientsSnapshot.forEach((doc32) => {
|
|
5352
|
+
patients.push(doc32.data());
|
|
5353
5353
|
});
|
|
5354
5354
|
console.log(`[getAllPatientsUtil] Found ${patients.length} patients`);
|
|
5355
5355
|
return patients;
|
|
@@ -5482,7 +5482,7 @@ var getActiveInviteTokensByClinicUtil = async (db, clinicId) => {
|
|
|
5482
5482
|
if (querySnapshot.empty) {
|
|
5483
5483
|
return [];
|
|
5484
5484
|
}
|
|
5485
|
-
return querySnapshot.docs.map((
|
|
5485
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
5486
5486
|
};
|
|
5487
5487
|
var getActiveInviteTokensByPatientUtil = async (db, patientId) => {
|
|
5488
5488
|
const tokensRef = collection9(
|
|
@@ -5500,7 +5500,7 @@ var getActiveInviteTokensByPatientUtil = async (db, patientId) => {
|
|
|
5500
5500
|
if (querySnapshot.empty) {
|
|
5501
5501
|
return [];
|
|
5502
5502
|
}
|
|
5503
|
-
return querySnapshot.docs.map((
|
|
5503
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
5504
5504
|
};
|
|
5505
5505
|
|
|
5506
5506
|
// src/services/patient/patient.service.ts
|
|
@@ -6623,7 +6623,7 @@ var PractitionerService = class extends BaseService {
|
|
|
6623
6623
|
where10("expiresAt", ">", Timestamp14.now())
|
|
6624
6624
|
);
|
|
6625
6625
|
const querySnapshot = await getDocs10(q);
|
|
6626
|
-
return querySnapshot.docs.map((
|
|
6626
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
6627
6627
|
}
|
|
6628
6628
|
/**
|
|
6629
6629
|
* Gets a token by its string value and validates it
|
|
@@ -6733,7 +6733,7 @@ var PractitionerService = class extends BaseService {
|
|
|
6733
6733
|
where10("status", "==", "active" /* ACTIVE */)
|
|
6734
6734
|
);
|
|
6735
6735
|
const querySnapshot = await getDocs10(q);
|
|
6736
|
-
return querySnapshot.docs.map((
|
|
6736
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
6737
6737
|
}
|
|
6738
6738
|
/**
|
|
6739
6739
|
* Dohvata sve zdravstvene radnike za određenu kliniku
|
|
@@ -6745,7 +6745,7 @@ var PractitionerService = class extends BaseService {
|
|
|
6745
6745
|
where10("isActive", "==", true)
|
|
6746
6746
|
);
|
|
6747
6747
|
const querySnapshot = await getDocs10(q);
|
|
6748
|
-
return querySnapshot.docs.map((
|
|
6748
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
6749
6749
|
}
|
|
6750
6750
|
/**
|
|
6751
6751
|
* Dohvata sve draft zdravstvene radnike za određenu kliniku sa statusom DRAFT
|
|
@@ -6757,7 +6757,7 @@ var PractitionerService = class extends BaseService {
|
|
|
6757
6757
|
where10("status", "==", "draft" /* DRAFT */)
|
|
6758
6758
|
);
|
|
6759
6759
|
const querySnapshot = await getDocs10(q);
|
|
6760
|
-
return querySnapshot.docs.map((
|
|
6760
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
6761
6761
|
}
|
|
6762
6762
|
/**
|
|
6763
6763
|
* Updates a practitioner
|
|
@@ -6971,7 +6971,7 @@ var PractitionerService = class extends BaseService {
|
|
|
6971
6971
|
);
|
|
6972
6972
|
const querySnapshot = await getDocs10(q);
|
|
6973
6973
|
const practitioners = querySnapshot.docs.map(
|
|
6974
|
-
(
|
|
6974
|
+
(doc32) => doc32.data()
|
|
6975
6975
|
);
|
|
6976
6976
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
6977
6977
|
return {
|
|
@@ -7042,8 +7042,8 @@ var PractitionerService = class extends BaseService {
|
|
|
7042
7042
|
console.log(
|
|
7043
7043
|
`[PRACTITIONER_SERVICE] Found ${querySnapshot.docs.length} practitioners with base query`
|
|
7044
7044
|
);
|
|
7045
|
-
let practitioners = querySnapshot.docs.map((
|
|
7046
|
-
return { ...
|
|
7045
|
+
let practitioners = querySnapshot.docs.map((doc32) => {
|
|
7046
|
+
return { ...doc32.data(), id: doc32.id };
|
|
7047
7047
|
});
|
|
7048
7048
|
const lastDoc = querySnapshot.docs.length > 0 ? querySnapshot.docs[querySnapshot.docs.length - 1] : null;
|
|
7049
7049
|
if (filters.nameSearch && filters.nameSearch.trim() !== "") {
|
|
@@ -7527,7 +7527,7 @@ var UserService = class extends BaseService {
|
|
|
7527
7527
|
];
|
|
7528
7528
|
const q = query11(collection11(this.db, USERS_COLLECTION), ...constraints);
|
|
7529
7529
|
const querySnapshot = await getDocs11(q);
|
|
7530
|
-
const users = querySnapshot.docs.map((
|
|
7530
|
+
const users = querySnapshot.docs.map((doc32) => doc32.data());
|
|
7531
7531
|
return users.map((userData) => userSchema.parse(userData));
|
|
7532
7532
|
}
|
|
7533
7533
|
/**
|
|
@@ -7907,7 +7907,7 @@ async function getAllActiveGroups(db) {
|
|
|
7907
7907
|
where12("isActive", "==", true)
|
|
7908
7908
|
);
|
|
7909
7909
|
const querySnapshot = await getDocs12(q);
|
|
7910
|
-
return querySnapshot.docs.map((
|
|
7910
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
7911
7911
|
}
|
|
7912
7912
|
async function updateClinicGroup(db, groupId, data, app) {
|
|
7913
7913
|
console.log("[CLINIC_GROUP] Updating clinic group", { groupId });
|
|
@@ -8372,7 +8372,7 @@ async function getClinicsByGroup(db, groupId) {
|
|
|
8372
8372
|
where13("isActive", "==", true)
|
|
8373
8373
|
);
|
|
8374
8374
|
const querySnapshot = await getDocs13(q);
|
|
8375
|
-
return querySnapshot.docs.map((
|
|
8375
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
8376
8376
|
}
|
|
8377
8377
|
async function updateClinic(db, clinicId, data, adminId, clinicAdminService, app) {
|
|
8378
8378
|
console.log("[CLINIC] Starting clinic update", { clinicId, adminId });
|
|
@@ -8566,7 +8566,7 @@ async function getClinicsByAdmin(db, adminId, options = {}, clinicAdminService,
|
|
|
8566
8566
|
}
|
|
8567
8567
|
const q = query13(collection13(db, CLINICS_COLLECTION), ...constraints);
|
|
8568
8568
|
const querySnapshot = await getDocs13(q);
|
|
8569
|
-
return querySnapshot.docs.map((
|
|
8569
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
8570
8570
|
}
|
|
8571
8571
|
async function getActiveClinicsByAdmin(db, adminId, clinicAdminService, clinicGroupService) {
|
|
8572
8572
|
return getClinicsByAdmin(
|
|
@@ -8611,11 +8611,11 @@ async function getAllClinics(db, pagination, lastDoc) {
|
|
|
8611
8611
|
}
|
|
8612
8612
|
const clinicsSnapshot = await getDocs13(clinicsQuery);
|
|
8613
8613
|
const lastVisible = clinicsSnapshot.docs[clinicsSnapshot.docs.length - 1];
|
|
8614
|
-
const clinics = clinicsSnapshot.docs.map((
|
|
8615
|
-
const data =
|
|
8614
|
+
const clinics = clinicsSnapshot.docs.map((doc32) => {
|
|
8615
|
+
const data = doc32.data();
|
|
8616
8616
|
return {
|
|
8617
8617
|
...data,
|
|
8618
|
-
id:
|
|
8618
|
+
id: doc32.id
|
|
8619
8619
|
};
|
|
8620
8620
|
});
|
|
8621
8621
|
return {
|
|
@@ -8642,8 +8642,8 @@ async function getAllClinicsInRange(db, center, rangeInKm, pagination, lastDoc)
|
|
|
8642
8642
|
];
|
|
8643
8643
|
const q = query13(collection13(db, CLINICS_COLLECTION), ...constraints);
|
|
8644
8644
|
const querySnapshot = await getDocs13(q);
|
|
8645
|
-
for (const
|
|
8646
|
-
const clinic =
|
|
8645
|
+
for (const doc32 of querySnapshot.docs) {
|
|
8646
|
+
const clinic = doc32.data();
|
|
8647
8647
|
const distance = distanceBetween2(
|
|
8648
8648
|
[center.latitude, center.longitude],
|
|
8649
8649
|
[clinic.location.latitude, clinic.location.longitude]
|
|
@@ -8765,8 +8765,8 @@ async function findClinicsInRadius(db, center, radiusInKm, filters) {
|
|
|
8765
8765
|
}
|
|
8766
8766
|
const q = query14(collection14(db, CLINICS_COLLECTION), ...constraints);
|
|
8767
8767
|
const querySnapshot = await getDocs14(q);
|
|
8768
|
-
for (const
|
|
8769
|
-
const clinic =
|
|
8768
|
+
for (const doc32 of querySnapshot.docs) {
|
|
8769
|
+
const clinic = doc32.data();
|
|
8770
8770
|
const distance = distanceBetween3(
|
|
8771
8771
|
[center.latitude, center.longitude],
|
|
8772
8772
|
[clinic.location.latitude, clinic.location.longitude]
|
|
@@ -8862,8 +8862,8 @@ async function getClinicsByFilters(db, filters) {
|
|
|
8862
8862
|
console.log(
|
|
8863
8863
|
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics in geo bound`
|
|
8864
8864
|
);
|
|
8865
|
-
for (const
|
|
8866
|
-
const clinic = { ...
|
|
8865
|
+
for (const doc32 of querySnapshot.docs) {
|
|
8866
|
+
const clinic = { ...doc32.data(), id: doc32.id };
|
|
8867
8867
|
const distance = distanceBetween4(
|
|
8868
8868
|
[center.latitude, center.longitude],
|
|
8869
8869
|
[clinic.location.latitude, clinic.location.longitude]
|
|
@@ -8919,8 +8919,8 @@ async function getClinicsByFilters(db, filters) {
|
|
|
8919
8919
|
console.log(
|
|
8920
8920
|
`[FILTER_UTILS] Found ${querySnapshot.docs.length} clinics with regular query`
|
|
8921
8921
|
);
|
|
8922
|
-
const clinics = querySnapshot.docs.map((
|
|
8923
|
-
return { ...
|
|
8922
|
+
const clinics = querySnapshot.docs.map((doc32) => {
|
|
8923
|
+
return { ...doc32.data(), id: doc32.id };
|
|
8924
8924
|
});
|
|
8925
8925
|
let filteredClinics = clinics;
|
|
8926
8926
|
if (filters.center) {
|
|
@@ -10238,14 +10238,23 @@ var AuthService = class extends BaseService {
|
|
|
10238
10238
|
}
|
|
10239
10239
|
};
|
|
10240
10240
|
|
|
10241
|
-
// src/services/calendar/calendar.
|
|
10242
|
-
import { Timestamp as
|
|
10243
|
-
import {
|
|
10241
|
+
// src/services/calendar/calendar.v2.service.ts
|
|
10242
|
+
import { Timestamp as Timestamp26, serverTimestamp as serverTimestamp20 } from "firebase/firestore";
|
|
10243
|
+
import {
|
|
10244
|
+
doc as doc23,
|
|
10245
|
+
getDoc as getDoc25,
|
|
10246
|
+
collection as collection23,
|
|
10247
|
+
query as query23,
|
|
10248
|
+
where as where23,
|
|
10249
|
+
getDocs as getDocs23,
|
|
10250
|
+
setDoc as setDoc21,
|
|
10251
|
+
updateDoc as updateDoc22
|
|
10252
|
+
} from "firebase/firestore";
|
|
10244
10253
|
|
|
10245
|
-
// src/services/calendar/utils/
|
|
10254
|
+
// src/services/calendar/utils/clinic.utils.ts
|
|
10246
10255
|
import {
|
|
10247
10256
|
collection as collection18,
|
|
10248
|
-
doc as
|
|
10257
|
+
doc as doc18,
|
|
10249
10258
|
getDoc as getDoc20,
|
|
10250
10259
|
getDocs as getDocs18,
|
|
10251
10260
|
setDoc as setDoc16,
|
|
@@ -10257,6 +10266,257 @@ import {
|
|
|
10257
10266
|
Timestamp as Timestamp20,
|
|
10258
10267
|
serverTimestamp as serverTimestamp15
|
|
10259
10268
|
} from "firebase/firestore";
|
|
10269
|
+
|
|
10270
|
+
// src/services/calendar/utils/docs.utils.ts
|
|
10271
|
+
import { doc as doc17 } from "firebase/firestore";
|
|
10272
|
+
function getPractitionerCalendarEventDocRef(db, practitionerId, eventId) {
|
|
10273
|
+
return doc17(
|
|
10274
|
+
db,
|
|
10275
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${CALENDAR_COLLECTION}/${eventId}`
|
|
10276
|
+
);
|
|
10277
|
+
}
|
|
10278
|
+
function getPatientCalendarEventDocRef(db, patientId, eventId) {
|
|
10279
|
+
return doc17(
|
|
10280
|
+
db,
|
|
10281
|
+
`${PATIENTS_COLLECTION}/${patientId}/${CALENDAR_COLLECTION}/${eventId}`
|
|
10282
|
+
);
|
|
10283
|
+
}
|
|
10284
|
+
function getClinicCalendarEventDocRef(db, clinicId, eventId) {
|
|
10285
|
+
return doc17(
|
|
10286
|
+
db,
|
|
10287
|
+
`${CLINICS_COLLECTION}/${clinicId}/${CALENDAR_COLLECTION}/${eventId}`
|
|
10288
|
+
);
|
|
10289
|
+
}
|
|
10290
|
+
function getPractitionerSyncedCalendarDocRef(db, practitionerId, syncedCalendarId) {
|
|
10291
|
+
return doc17(
|
|
10292
|
+
db,
|
|
10293
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/syncedCalendars/${syncedCalendarId}`
|
|
10294
|
+
);
|
|
10295
|
+
}
|
|
10296
|
+
function getPatientSyncedCalendarDocRef(db, patientId, syncedCalendarId) {
|
|
10297
|
+
return doc17(
|
|
10298
|
+
db,
|
|
10299
|
+
`${PATIENTS_COLLECTION}/${patientId}/syncedCalendars/${syncedCalendarId}`
|
|
10300
|
+
);
|
|
10301
|
+
}
|
|
10302
|
+
function getClinicSyncedCalendarDocRef(db, clinicId, syncedCalendarId) {
|
|
10303
|
+
return doc17(
|
|
10304
|
+
db,
|
|
10305
|
+
`${CLINICS_COLLECTION}/${clinicId}/syncedCalendars/${syncedCalendarId}`
|
|
10306
|
+
);
|
|
10307
|
+
}
|
|
10308
|
+
|
|
10309
|
+
// src/services/calendar/utils/clinic.utils.ts
|
|
10310
|
+
async function createClinicCalendarEventUtil(db, clinicId, eventData, generateId2) {
|
|
10311
|
+
const eventId = generateId2();
|
|
10312
|
+
const eventRef = getClinicCalendarEventDocRef(db, clinicId, eventId);
|
|
10313
|
+
const newEvent = {
|
|
10314
|
+
id: eventId,
|
|
10315
|
+
...eventData,
|
|
10316
|
+
createdAt: serverTimestamp15(),
|
|
10317
|
+
updatedAt: serverTimestamp15()
|
|
10318
|
+
};
|
|
10319
|
+
await setDoc16(eventRef, newEvent);
|
|
10320
|
+
return {
|
|
10321
|
+
...newEvent,
|
|
10322
|
+
createdAt: Timestamp20.now(),
|
|
10323
|
+
updatedAt: Timestamp20.now()
|
|
10324
|
+
};
|
|
10325
|
+
}
|
|
10326
|
+
async function updateClinicCalendarEventUtil(db, clinicId, eventId, updateData) {
|
|
10327
|
+
const eventRef = getClinicCalendarEventDocRef(db, clinicId, eventId);
|
|
10328
|
+
const updates = {
|
|
10329
|
+
...updateData,
|
|
10330
|
+
updatedAt: serverTimestamp15()
|
|
10331
|
+
};
|
|
10332
|
+
await updateDoc17(eventRef, updates);
|
|
10333
|
+
const updatedDoc = await getDoc20(eventRef);
|
|
10334
|
+
if (!updatedDoc.exists()) {
|
|
10335
|
+
throw new Error("Event not found after update");
|
|
10336
|
+
}
|
|
10337
|
+
return updatedDoc.data();
|
|
10338
|
+
}
|
|
10339
|
+
async function checkAutoConfirmAppointmentsUtil(db, clinicId) {
|
|
10340
|
+
const clinicDoc = await getDoc20(doc18(db, `clinics/${clinicId}`));
|
|
10341
|
+
if (!clinicDoc.exists()) {
|
|
10342
|
+
throw new Error(`Clinic with ID ${clinicId} not found`);
|
|
10343
|
+
}
|
|
10344
|
+
const clinicData = clinicDoc.data();
|
|
10345
|
+
const clinicGroupId = clinicData.clinicGroupId;
|
|
10346
|
+
if (!clinicGroupId) {
|
|
10347
|
+
return false;
|
|
10348
|
+
}
|
|
10349
|
+
const clinicGroupDoc = await getDoc20(
|
|
10350
|
+
doc18(db, `${CLINIC_GROUPS_COLLECTION}/${clinicGroupId}`)
|
|
10351
|
+
);
|
|
10352
|
+
if (!clinicGroupDoc.exists()) {
|
|
10353
|
+
return false;
|
|
10354
|
+
}
|
|
10355
|
+
const clinicGroupData = clinicGroupDoc.data();
|
|
10356
|
+
return !!clinicGroupData.autoConfirmAppointments;
|
|
10357
|
+
}
|
|
10358
|
+
|
|
10359
|
+
// src/services/calendar/utils/patient.utils.ts
|
|
10360
|
+
import {
|
|
10361
|
+
collection as collection19,
|
|
10362
|
+
getDoc as getDoc21,
|
|
10363
|
+
getDocs as getDocs19,
|
|
10364
|
+
setDoc as setDoc17,
|
|
10365
|
+
updateDoc as updateDoc18,
|
|
10366
|
+
deleteDoc as deleteDoc10,
|
|
10367
|
+
query as query19,
|
|
10368
|
+
where as where19,
|
|
10369
|
+
orderBy as orderBy8,
|
|
10370
|
+
Timestamp as Timestamp21,
|
|
10371
|
+
serverTimestamp as serverTimestamp16
|
|
10372
|
+
} from "firebase/firestore";
|
|
10373
|
+
async function createPatientCalendarEventUtil(db, patientId, eventData, generateId2) {
|
|
10374
|
+
const eventId = generateId2();
|
|
10375
|
+
const eventRef = getPatientCalendarEventDocRef(db, patientId, eventId);
|
|
10376
|
+
const newEvent = {
|
|
10377
|
+
id: eventId,
|
|
10378
|
+
...eventData,
|
|
10379
|
+
createdAt: serverTimestamp16(),
|
|
10380
|
+
updatedAt: serverTimestamp16()
|
|
10381
|
+
};
|
|
10382
|
+
await setDoc17(eventRef, newEvent);
|
|
10383
|
+
return {
|
|
10384
|
+
...newEvent,
|
|
10385
|
+
createdAt: Timestamp21.now(),
|
|
10386
|
+
updatedAt: Timestamp21.now()
|
|
10387
|
+
};
|
|
10388
|
+
}
|
|
10389
|
+
async function updatePatientCalendarEventUtil(db, patientId, eventId, updateData) {
|
|
10390
|
+
const eventRef = getPatientCalendarEventDocRef(db, patientId, eventId);
|
|
10391
|
+
const updates = {
|
|
10392
|
+
...updateData,
|
|
10393
|
+
updatedAt: serverTimestamp16()
|
|
10394
|
+
};
|
|
10395
|
+
await updateDoc18(eventRef, updates);
|
|
10396
|
+
const updatedDoc = await getDoc21(eventRef);
|
|
10397
|
+
if (!updatedDoc.exists()) {
|
|
10398
|
+
throw new Error("Event not found after update");
|
|
10399
|
+
}
|
|
10400
|
+
return updatedDoc.data();
|
|
10401
|
+
}
|
|
10402
|
+
|
|
10403
|
+
// src/services/calendar/utils/practitioner.utils.ts
|
|
10404
|
+
import {
|
|
10405
|
+
collection as collection20,
|
|
10406
|
+
getDoc as getDoc22,
|
|
10407
|
+
getDocs as getDocs20,
|
|
10408
|
+
setDoc as setDoc18,
|
|
10409
|
+
updateDoc as updateDoc19,
|
|
10410
|
+
deleteDoc as deleteDoc11,
|
|
10411
|
+
query as query20,
|
|
10412
|
+
where as where20,
|
|
10413
|
+
orderBy as orderBy9,
|
|
10414
|
+
Timestamp as Timestamp22,
|
|
10415
|
+
serverTimestamp as serverTimestamp17
|
|
10416
|
+
} from "firebase/firestore";
|
|
10417
|
+
async function createPractitionerCalendarEventUtil(db, practitionerId, eventData, generateId2) {
|
|
10418
|
+
const eventId = generateId2();
|
|
10419
|
+
const eventRef = getPractitionerCalendarEventDocRef(
|
|
10420
|
+
db,
|
|
10421
|
+
practitionerId,
|
|
10422
|
+
eventId
|
|
10423
|
+
);
|
|
10424
|
+
const newEvent = {
|
|
10425
|
+
id: eventId,
|
|
10426
|
+
...eventData,
|
|
10427
|
+
createdAt: serverTimestamp17(),
|
|
10428
|
+
updatedAt: serverTimestamp17()
|
|
10429
|
+
};
|
|
10430
|
+
await setDoc18(eventRef, newEvent);
|
|
10431
|
+
return {
|
|
10432
|
+
...newEvent,
|
|
10433
|
+
createdAt: Timestamp22.now(),
|
|
10434
|
+
updatedAt: Timestamp22.now()
|
|
10435
|
+
};
|
|
10436
|
+
}
|
|
10437
|
+
async function updatePractitionerCalendarEventUtil(db, practitionerId, eventId, updateData) {
|
|
10438
|
+
const eventRef = getPractitionerCalendarEventDocRef(
|
|
10439
|
+
db,
|
|
10440
|
+
practitionerId,
|
|
10441
|
+
eventId
|
|
10442
|
+
);
|
|
10443
|
+
const updates = {
|
|
10444
|
+
...updateData,
|
|
10445
|
+
updatedAt: serverTimestamp17()
|
|
10446
|
+
};
|
|
10447
|
+
await updateDoc19(eventRef, updates);
|
|
10448
|
+
const updatedDoc = await getDoc22(eventRef);
|
|
10449
|
+
if (!updatedDoc.exists()) {
|
|
10450
|
+
throw new Error("Event not found after update");
|
|
10451
|
+
}
|
|
10452
|
+
return updatedDoc.data();
|
|
10453
|
+
}
|
|
10454
|
+
|
|
10455
|
+
// src/services/calendar/utils/appointment.utils.ts
|
|
10456
|
+
async function createAppointmentUtil2(db, clinicId, practitionerId, patientId, eventData, generateId2) {
|
|
10457
|
+
const eventId = generateId2();
|
|
10458
|
+
const autoConfirm = await checkAutoConfirmAppointmentsUtil(db, clinicId);
|
|
10459
|
+
const initialStatus = autoConfirm ? "confirmed" /* CONFIRMED */ : "pending" /* PENDING */;
|
|
10460
|
+
const appointmentData = {
|
|
10461
|
+
...eventData,
|
|
10462
|
+
clinicBranchId: clinicId,
|
|
10463
|
+
practitionerProfileId: practitionerId,
|
|
10464
|
+
patientProfileId: patientId,
|
|
10465
|
+
eventType: "appointment" /* APPOINTMENT */,
|
|
10466
|
+
status: eventData.status || initialStatus
|
|
10467
|
+
};
|
|
10468
|
+
const clinicPromise = createClinicCalendarEventUtil(
|
|
10469
|
+
db,
|
|
10470
|
+
clinicId,
|
|
10471
|
+
appointmentData,
|
|
10472
|
+
() => eventId
|
|
10473
|
+
// Use the same ID for all calendars
|
|
10474
|
+
);
|
|
10475
|
+
const practitionerPromise = createPractitionerCalendarEventUtil(
|
|
10476
|
+
db,
|
|
10477
|
+
practitionerId,
|
|
10478
|
+
appointmentData,
|
|
10479
|
+
() => eventId
|
|
10480
|
+
// Use the same ID for all calendars
|
|
10481
|
+
);
|
|
10482
|
+
const patientPromise = createPatientCalendarEventUtil(
|
|
10483
|
+
db,
|
|
10484
|
+
patientId,
|
|
10485
|
+
appointmentData,
|
|
10486
|
+
() => eventId
|
|
10487
|
+
// Use the same ID for all calendars
|
|
10488
|
+
);
|
|
10489
|
+
const [clinicEvent] = await Promise.all([clinicPromise, practitionerPromise, patientPromise]);
|
|
10490
|
+
return clinicEvent;
|
|
10491
|
+
}
|
|
10492
|
+
async function updateAppointmentUtil2(db, clinicId, practitionerId, patientId, eventId, updateData) {
|
|
10493
|
+
const clinicPromise = updateClinicCalendarEventUtil(db, clinicId, eventId, updateData);
|
|
10494
|
+
const practitionerPromise = updatePractitionerCalendarEventUtil(
|
|
10495
|
+
db,
|
|
10496
|
+
practitionerId,
|
|
10497
|
+
eventId,
|
|
10498
|
+
updateData
|
|
10499
|
+
);
|
|
10500
|
+
const patientPromise = updatePatientCalendarEventUtil(db, patientId, eventId, updateData);
|
|
10501
|
+
const [clinicEvent] = await Promise.all([clinicPromise, practitionerPromise, patientPromise]);
|
|
10502
|
+
return clinicEvent;
|
|
10503
|
+
}
|
|
10504
|
+
|
|
10505
|
+
// src/services/calendar/utils/calendar-event.utils.ts
|
|
10506
|
+
import {
|
|
10507
|
+
collection as collection21,
|
|
10508
|
+
doc as doc21,
|
|
10509
|
+
getDoc as getDoc23,
|
|
10510
|
+
getDocs as getDocs21,
|
|
10511
|
+
setDoc as setDoc19,
|
|
10512
|
+
updateDoc as updateDoc20,
|
|
10513
|
+
deleteDoc as deleteDoc12,
|
|
10514
|
+
query as query21,
|
|
10515
|
+
where as where21,
|
|
10516
|
+
orderBy as orderBy10,
|
|
10517
|
+
Timestamp as Timestamp23,
|
|
10518
|
+
serverTimestamp as serverTimestamp18
|
|
10519
|
+
} from "firebase/firestore";
|
|
10260
10520
|
async function searchCalendarEventsUtil(db, params) {
|
|
10261
10521
|
const { searchLocation, entityId, ...filters } = params;
|
|
10262
10522
|
let baseCollectionPath;
|
|
@@ -10268,88 +10528,2323 @@ async function searchCalendarEventsUtil(db, params) {
|
|
|
10268
10528
|
"Practitioner ID (entityId) is required when searching practitioner calendar."
|
|
10269
10529
|
);
|
|
10270
10530
|
}
|
|
10271
|
-
baseCollectionPath = `${PRACTITIONERS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
|
|
10272
|
-
if (filters.practitionerId && filters.practitionerId !== entityId) {
|
|
10273
|
-
console.warn(
|
|
10274
|
-
`Provided practitionerId filter (${filters.practitionerId}) does not match search entityId (${entityId}). Returning empty results.`
|
|
10275
|
-
);
|
|
10276
|
-
return [];
|
|
10531
|
+
baseCollectionPath = `${PRACTITIONERS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
|
|
10532
|
+
if (filters.practitionerId && filters.practitionerId !== entityId) {
|
|
10533
|
+
console.warn(
|
|
10534
|
+
`Provided practitionerId filter (${filters.practitionerId}) does not match search entityId (${entityId}). Returning empty results.`
|
|
10535
|
+
);
|
|
10536
|
+
return [];
|
|
10537
|
+
}
|
|
10538
|
+
filters.practitionerId = void 0;
|
|
10539
|
+
break;
|
|
10540
|
+
case "patient" /* PATIENT */:
|
|
10541
|
+
if (!entityId) {
|
|
10542
|
+
throw new Error(
|
|
10543
|
+
"Patient ID (entityId) is required when searching patient calendar."
|
|
10544
|
+
);
|
|
10545
|
+
}
|
|
10546
|
+
baseCollectionPath = `${PATIENTS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
|
|
10547
|
+
if (filters.patientId && filters.patientId !== entityId) {
|
|
10548
|
+
console.warn(
|
|
10549
|
+
`Provided patientId filter (${filters.patientId}) does not match search entityId (${entityId}). Returning empty results.`
|
|
10550
|
+
);
|
|
10551
|
+
return [];
|
|
10552
|
+
}
|
|
10553
|
+
filters.patientId = void 0;
|
|
10554
|
+
break;
|
|
10555
|
+
case "clinic" /* CLINIC */:
|
|
10556
|
+
if (!entityId) {
|
|
10557
|
+
throw new Error(
|
|
10558
|
+
"Clinic ID (entityId) is required when searching clinic-related events."
|
|
10559
|
+
);
|
|
10560
|
+
}
|
|
10561
|
+
baseCollectionPath = `${CLINICS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
|
|
10562
|
+
constraints.push(where21("clinicBranchId", "==", entityId));
|
|
10563
|
+
if (filters.clinicId && filters.clinicId !== entityId) {
|
|
10564
|
+
console.warn(
|
|
10565
|
+
`Provided clinicId filter (${filters.clinicId}) does not match search entityId (${entityId}). Returning empty results.`
|
|
10566
|
+
);
|
|
10567
|
+
return [];
|
|
10568
|
+
}
|
|
10569
|
+
filters.clinicId = void 0;
|
|
10570
|
+
break;
|
|
10571
|
+
default:
|
|
10572
|
+
throw new Error(`Invalid search location: ${searchLocation}`);
|
|
10573
|
+
}
|
|
10574
|
+
const collectionRef = collection21(db, baseCollectionPath);
|
|
10575
|
+
if (filters.clinicId) {
|
|
10576
|
+
constraints.push(where21("clinicBranchId", "==", filters.clinicId));
|
|
10577
|
+
}
|
|
10578
|
+
if (filters.practitionerId) {
|
|
10579
|
+
constraints.push(
|
|
10580
|
+
where21("practitionerProfileId", "==", filters.practitionerId)
|
|
10581
|
+
);
|
|
10582
|
+
}
|
|
10583
|
+
if (filters.patientId) {
|
|
10584
|
+
constraints.push(where21("patientProfileId", "==", filters.patientId));
|
|
10585
|
+
}
|
|
10586
|
+
if (filters.procedureId) {
|
|
10587
|
+
constraints.push(where21("procedureId", "==", filters.procedureId));
|
|
10588
|
+
}
|
|
10589
|
+
if (filters.eventStatus) {
|
|
10590
|
+
constraints.push(where21("status", "==", filters.eventStatus));
|
|
10591
|
+
}
|
|
10592
|
+
if (filters.eventType) {
|
|
10593
|
+
constraints.push(where21("eventType", "==", filters.eventType));
|
|
10594
|
+
}
|
|
10595
|
+
if (filters.dateRange) {
|
|
10596
|
+
constraints.push(where21("eventTime.start", ">=", filters.dateRange.start));
|
|
10597
|
+
constraints.push(where21("eventTime.start", "<=", filters.dateRange.end));
|
|
10598
|
+
}
|
|
10599
|
+
try {
|
|
10600
|
+
const finalQuery = query21(collectionRef, ...constraints);
|
|
10601
|
+
const querySnapshot = await getDocs21(finalQuery);
|
|
10602
|
+
const events = querySnapshot.docs.map(
|
|
10603
|
+
(doc32) => ({ id: doc32.id, ...doc32.data() })
|
|
10604
|
+
);
|
|
10605
|
+
return events;
|
|
10606
|
+
} catch (error) {
|
|
10607
|
+
console.error("Error searching calendar events:", error);
|
|
10608
|
+
return [];
|
|
10609
|
+
}
|
|
10610
|
+
}
|
|
10611
|
+
|
|
10612
|
+
// src/services/calendar/utils/synced-calendar.utils.ts
|
|
10613
|
+
import {
|
|
10614
|
+
collection as collection22,
|
|
10615
|
+
getDoc as getDoc24,
|
|
10616
|
+
getDocs as getDocs22,
|
|
10617
|
+
setDoc as setDoc20,
|
|
10618
|
+
updateDoc as updateDoc21,
|
|
10619
|
+
deleteDoc as deleteDoc13,
|
|
10620
|
+
query as query22,
|
|
10621
|
+
orderBy as orderBy11,
|
|
10622
|
+
Timestamp as Timestamp24,
|
|
10623
|
+
serverTimestamp as serverTimestamp19
|
|
10624
|
+
} from "firebase/firestore";
|
|
10625
|
+
async function createPractitionerSyncedCalendarUtil(db, practitionerId, calendarData, generateId2) {
|
|
10626
|
+
const calendarId = generateId2();
|
|
10627
|
+
const calendarRef = getPractitionerSyncedCalendarDocRef(
|
|
10628
|
+
db,
|
|
10629
|
+
practitionerId,
|
|
10630
|
+
calendarId
|
|
10631
|
+
);
|
|
10632
|
+
const newCalendar = {
|
|
10633
|
+
id: calendarId,
|
|
10634
|
+
...calendarData,
|
|
10635
|
+
createdAt: serverTimestamp19(),
|
|
10636
|
+
updatedAt: serverTimestamp19()
|
|
10637
|
+
};
|
|
10638
|
+
await setDoc20(calendarRef, newCalendar);
|
|
10639
|
+
return {
|
|
10640
|
+
...newCalendar,
|
|
10641
|
+
createdAt: Timestamp24.now(),
|
|
10642
|
+
updatedAt: Timestamp24.now()
|
|
10643
|
+
};
|
|
10644
|
+
}
|
|
10645
|
+
async function createPatientSyncedCalendarUtil(db, patientId, calendarData, generateId2) {
|
|
10646
|
+
const calendarId = generateId2();
|
|
10647
|
+
const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
|
|
10648
|
+
const newCalendar = {
|
|
10649
|
+
id: calendarId,
|
|
10650
|
+
...calendarData,
|
|
10651
|
+
createdAt: serverTimestamp19(),
|
|
10652
|
+
updatedAt: serverTimestamp19()
|
|
10653
|
+
};
|
|
10654
|
+
await setDoc20(calendarRef, newCalendar);
|
|
10655
|
+
return {
|
|
10656
|
+
...newCalendar,
|
|
10657
|
+
createdAt: Timestamp24.now(),
|
|
10658
|
+
updatedAt: Timestamp24.now()
|
|
10659
|
+
};
|
|
10660
|
+
}
|
|
10661
|
+
async function createClinicSyncedCalendarUtil(db, clinicId, calendarData, generateId2) {
|
|
10662
|
+
const calendarId = generateId2();
|
|
10663
|
+
const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
|
|
10664
|
+
const newCalendar = {
|
|
10665
|
+
id: calendarId,
|
|
10666
|
+
...calendarData,
|
|
10667
|
+
createdAt: serverTimestamp19(),
|
|
10668
|
+
updatedAt: serverTimestamp19()
|
|
10669
|
+
};
|
|
10670
|
+
await setDoc20(calendarRef, newCalendar);
|
|
10671
|
+
return {
|
|
10672
|
+
...newCalendar,
|
|
10673
|
+
createdAt: Timestamp24.now(),
|
|
10674
|
+
updatedAt: Timestamp24.now()
|
|
10675
|
+
};
|
|
10676
|
+
}
|
|
10677
|
+
async function getPractitionerSyncedCalendarUtil(db, practitionerId, calendarId) {
|
|
10678
|
+
const calendarRef = getPractitionerSyncedCalendarDocRef(
|
|
10679
|
+
db,
|
|
10680
|
+
practitionerId,
|
|
10681
|
+
calendarId
|
|
10682
|
+
);
|
|
10683
|
+
const calendarDoc = await getDoc24(calendarRef);
|
|
10684
|
+
if (!calendarDoc.exists()) {
|
|
10685
|
+
return null;
|
|
10686
|
+
}
|
|
10687
|
+
return calendarDoc.data();
|
|
10688
|
+
}
|
|
10689
|
+
async function getPractitionerSyncedCalendarsUtil(db, practitionerId) {
|
|
10690
|
+
const calendarsRef = collection22(
|
|
10691
|
+
db,
|
|
10692
|
+
`practitioners/${practitionerId}/${SYNCED_CALENDARS_COLLECTION}`
|
|
10693
|
+
);
|
|
10694
|
+
const q = query22(calendarsRef, orderBy11("createdAt", "desc"));
|
|
10695
|
+
const querySnapshot = await getDocs22(q);
|
|
10696
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
10697
|
+
}
|
|
10698
|
+
async function getPatientSyncedCalendarUtil(db, patientId, calendarId) {
|
|
10699
|
+
const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
|
|
10700
|
+
const calendarDoc = await getDoc24(calendarRef);
|
|
10701
|
+
if (!calendarDoc.exists()) {
|
|
10702
|
+
return null;
|
|
10703
|
+
}
|
|
10704
|
+
return calendarDoc.data();
|
|
10705
|
+
}
|
|
10706
|
+
async function getPatientSyncedCalendarsUtil(db, patientId) {
|
|
10707
|
+
const calendarsRef = collection22(
|
|
10708
|
+
db,
|
|
10709
|
+
`patients/${patientId}/${SYNCED_CALENDARS_COLLECTION}`
|
|
10710
|
+
);
|
|
10711
|
+
const q = query22(calendarsRef, orderBy11("createdAt", "desc"));
|
|
10712
|
+
const querySnapshot = await getDocs22(q);
|
|
10713
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
10714
|
+
}
|
|
10715
|
+
async function getClinicSyncedCalendarUtil(db, clinicId, calendarId) {
|
|
10716
|
+
const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
|
|
10717
|
+
const calendarDoc = await getDoc24(calendarRef);
|
|
10718
|
+
if (!calendarDoc.exists()) {
|
|
10719
|
+
return null;
|
|
10720
|
+
}
|
|
10721
|
+
return calendarDoc.data();
|
|
10722
|
+
}
|
|
10723
|
+
async function getClinicSyncedCalendarsUtil(db, clinicId) {
|
|
10724
|
+
const calendarsRef = collection22(
|
|
10725
|
+
db,
|
|
10726
|
+
`clinics/${clinicId}/${SYNCED_CALENDARS_COLLECTION}`
|
|
10727
|
+
);
|
|
10728
|
+
const q = query22(calendarsRef, orderBy11("createdAt", "desc"));
|
|
10729
|
+
const querySnapshot = await getDocs22(q);
|
|
10730
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
10731
|
+
}
|
|
10732
|
+
async function updatePractitionerSyncedCalendarUtil(db, practitionerId, calendarId, updateData) {
|
|
10733
|
+
const calendarRef = getPractitionerSyncedCalendarDocRef(
|
|
10734
|
+
db,
|
|
10735
|
+
practitionerId,
|
|
10736
|
+
calendarId
|
|
10737
|
+
);
|
|
10738
|
+
const updates = {
|
|
10739
|
+
...updateData,
|
|
10740
|
+
updatedAt: serverTimestamp19()
|
|
10741
|
+
};
|
|
10742
|
+
await updateDoc21(calendarRef, updates);
|
|
10743
|
+
const updatedDoc = await getDoc24(calendarRef);
|
|
10744
|
+
if (!updatedDoc.exists()) {
|
|
10745
|
+
throw new Error("Synced calendar not found after update");
|
|
10746
|
+
}
|
|
10747
|
+
return updatedDoc.data();
|
|
10748
|
+
}
|
|
10749
|
+
async function updatePatientSyncedCalendarUtil(db, patientId, calendarId, updateData) {
|
|
10750
|
+
const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
|
|
10751
|
+
const updates = {
|
|
10752
|
+
...updateData,
|
|
10753
|
+
updatedAt: serverTimestamp19()
|
|
10754
|
+
};
|
|
10755
|
+
await updateDoc21(calendarRef, updates);
|
|
10756
|
+
const updatedDoc = await getDoc24(calendarRef);
|
|
10757
|
+
if (!updatedDoc.exists()) {
|
|
10758
|
+
throw new Error("Synced calendar not found after update");
|
|
10759
|
+
}
|
|
10760
|
+
return updatedDoc.data();
|
|
10761
|
+
}
|
|
10762
|
+
async function updateClinicSyncedCalendarUtil(db, clinicId, calendarId, updateData) {
|
|
10763
|
+
const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
|
|
10764
|
+
const updates = {
|
|
10765
|
+
...updateData,
|
|
10766
|
+
updatedAt: serverTimestamp19()
|
|
10767
|
+
};
|
|
10768
|
+
await updateDoc21(calendarRef, updates);
|
|
10769
|
+
const updatedDoc = await getDoc24(calendarRef);
|
|
10770
|
+
if (!updatedDoc.exists()) {
|
|
10771
|
+
throw new Error("Synced calendar not found after update");
|
|
10772
|
+
}
|
|
10773
|
+
return updatedDoc.data();
|
|
10774
|
+
}
|
|
10775
|
+
async function deletePractitionerSyncedCalendarUtil(db, practitionerId, calendarId) {
|
|
10776
|
+
const calendarRef = getPractitionerSyncedCalendarDocRef(
|
|
10777
|
+
db,
|
|
10778
|
+
practitionerId,
|
|
10779
|
+
calendarId
|
|
10780
|
+
);
|
|
10781
|
+
await deleteDoc13(calendarRef);
|
|
10782
|
+
}
|
|
10783
|
+
async function deletePatientSyncedCalendarUtil(db, patientId, calendarId) {
|
|
10784
|
+
const calendarRef = getPatientSyncedCalendarDocRef(db, patientId, calendarId);
|
|
10785
|
+
await deleteDoc13(calendarRef);
|
|
10786
|
+
}
|
|
10787
|
+
async function deleteClinicSyncedCalendarUtil(db, clinicId, calendarId) {
|
|
10788
|
+
const calendarRef = getClinicSyncedCalendarDocRef(db, clinicId, calendarId);
|
|
10789
|
+
await deleteDoc13(calendarRef);
|
|
10790
|
+
}
|
|
10791
|
+
async function updateLastSyncedTimestampUtil(db, entityType, entityId, calendarId) {
|
|
10792
|
+
const updateData = {
|
|
10793
|
+
lastSyncedAt: Timestamp24.now()
|
|
10794
|
+
};
|
|
10795
|
+
switch (entityType) {
|
|
10796
|
+
case "practitioner":
|
|
10797
|
+
return updatePractitionerSyncedCalendarUtil(
|
|
10798
|
+
db,
|
|
10799
|
+
entityId,
|
|
10800
|
+
calendarId,
|
|
10801
|
+
updateData
|
|
10802
|
+
);
|
|
10803
|
+
case "patient":
|
|
10804
|
+
return updatePatientSyncedCalendarUtil(
|
|
10805
|
+
db,
|
|
10806
|
+
entityId,
|
|
10807
|
+
calendarId,
|
|
10808
|
+
updateData
|
|
10809
|
+
);
|
|
10810
|
+
case "clinic":
|
|
10811
|
+
return updateClinicSyncedCalendarUtil(
|
|
10812
|
+
db,
|
|
10813
|
+
entityId,
|
|
10814
|
+
calendarId,
|
|
10815
|
+
updateData
|
|
10816
|
+
);
|
|
10817
|
+
default:
|
|
10818
|
+
throw new Error(`Invalid entity type: ${entityType}`);
|
|
10819
|
+
}
|
|
10820
|
+
}
|
|
10821
|
+
|
|
10822
|
+
// src/services/calendar/utils/google-calendar.utils.ts
|
|
10823
|
+
import { Timestamp as Timestamp25 } from "firebase/firestore";
|
|
10824
|
+
var GOOGLE_CALENDAR_API_URL = "https://www.googleapis.com/calendar/v3";
|
|
10825
|
+
var GOOGLE_OAUTH_URL = "https://oauth2.googleapis.com/token";
|
|
10826
|
+
var CLIENT_ID = "your-client-id";
|
|
10827
|
+
var CLIENT_SECRET = "your-client-secret";
|
|
10828
|
+
var REDIRECT_URI = "your-redirect-uri";
|
|
10829
|
+
async function makeRequest(method, url, headers, data, params) {
|
|
10830
|
+
const queryParams = params ? "?" + Object.entries(params).map(
|
|
10831
|
+
([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
|
|
10832
|
+
).join("&") : "";
|
|
10833
|
+
const finalUrl = url + queryParams;
|
|
10834
|
+
const options = {
|
|
10835
|
+
method,
|
|
10836
|
+
headers,
|
|
10837
|
+
body: data ? JSON.stringify(data) : void 0
|
|
10838
|
+
};
|
|
10839
|
+
const response = await fetch(finalUrl, options);
|
|
10840
|
+
if (!response.ok) {
|
|
10841
|
+
const error = new Error(
|
|
10842
|
+
`Request failed with status ${response.status}`
|
|
10843
|
+
);
|
|
10844
|
+
error.response = response;
|
|
10845
|
+
throw error;
|
|
10846
|
+
}
|
|
10847
|
+
return response.json();
|
|
10848
|
+
}
|
|
10849
|
+
async function authenticateWithGoogleCalendarUtil(authCode) {
|
|
10850
|
+
try {
|
|
10851
|
+
const data = {
|
|
10852
|
+
code: authCode,
|
|
10853
|
+
client_id: CLIENT_ID,
|
|
10854
|
+
client_secret: CLIENT_SECRET,
|
|
10855
|
+
redirect_uri: REDIRECT_URI,
|
|
10856
|
+
grant_type: "authorization_code"
|
|
10857
|
+
};
|
|
10858
|
+
const response = await makeRequest(
|
|
10859
|
+
"post",
|
|
10860
|
+
GOOGLE_OAUTH_URL,
|
|
10861
|
+
{ "Content-Type": "application/json" },
|
|
10862
|
+
data
|
|
10863
|
+
);
|
|
10864
|
+
return {
|
|
10865
|
+
accessToken: response.access_token,
|
|
10866
|
+
refreshToken: response.refresh_token,
|
|
10867
|
+
expiresIn: response.expires_in
|
|
10868
|
+
};
|
|
10869
|
+
} catch (error) {
|
|
10870
|
+
const apiError = error;
|
|
10871
|
+
console.error(
|
|
10872
|
+
"Error authenticating with Google Calendar:",
|
|
10873
|
+
apiError.message || "Unknown error"
|
|
10874
|
+
);
|
|
10875
|
+
throw new Error(
|
|
10876
|
+
`Failed to authenticate with Google Calendar: ${apiError.message || "Unknown error"}`
|
|
10877
|
+
);
|
|
10878
|
+
}
|
|
10879
|
+
}
|
|
10880
|
+
async function refreshGoogleCalendarTokenUtil(refreshToken) {
|
|
10881
|
+
try {
|
|
10882
|
+
const data = {
|
|
10883
|
+
refresh_token: refreshToken,
|
|
10884
|
+
client_id: CLIENT_ID,
|
|
10885
|
+
client_secret: CLIENT_SECRET,
|
|
10886
|
+
grant_type: "refresh_token"
|
|
10887
|
+
};
|
|
10888
|
+
const response = await makeRequest(
|
|
10889
|
+
"post",
|
|
10890
|
+
GOOGLE_OAUTH_URL,
|
|
10891
|
+
{ "Content-Type": "application/json" },
|
|
10892
|
+
data
|
|
10893
|
+
);
|
|
10894
|
+
return {
|
|
10895
|
+
accessToken: response.access_token,
|
|
10896
|
+
expiresIn: response.expires_in
|
|
10897
|
+
};
|
|
10898
|
+
} catch (error) {
|
|
10899
|
+
const apiError = error;
|
|
10900
|
+
console.error(
|
|
10901
|
+
"Error refreshing Google Calendar token:",
|
|
10902
|
+
apiError.message || "Unknown error"
|
|
10903
|
+
);
|
|
10904
|
+
throw new Error(
|
|
10905
|
+
`Failed to refresh Google Calendar token: ${apiError.message || "Unknown error"}`
|
|
10906
|
+
);
|
|
10907
|
+
}
|
|
10908
|
+
}
|
|
10909
|
+
async function listGoogleCalendarsUtil(accessToken) {
|
|
10910
|
+
try {
|
|
10911
|
+
const response = await makeRequest(
|
|
10912
|
+
"get",
|
|
10913
|
+
`${GOOGLE_CALENDAR_API_URL}/users/me/calendarList`,
|
|
10914
|
+
{ Authorization: `Bearer ${accessToken}` }
|
|
10915
|
+
);
|
|
10916
|
+
return response.items.map((calendar) => ({
|
|
10917
|
+
id: calendar.id,
|
|
10918
|
+
name: calendar.summary
|
|
10919
|
+
}));
|
|
10920
|
+
} catch (error) {
|
|
10921
|
+
const apiError = error;
|
|
10922
|
+
console.error(
|
|
10923
|
+
"Error listing Google Calendars:",
|
|
10924
|
+
apiError.message || "Unknown error"
|
|
10925
|
+
);
|
|
10926
|
+
throw new Error(
|
|
10927
|
+
`Failed to list Google Calendars: ${apiError.message || "Unknown error"}`
|
|
10928
|
+
);
|
|
10929
|
+
}
|
|
10930
|
+
}
|
|
10931
|
+
async function ensureValidToken(db, entityType, entityId, syncedCalendar) {
|
|
10932
|
+
const expiryTime = syncedCalendar.tokenExpiry.toDate();
|
|
10933
|
+
const now = /* @__PURE__ */ new Date();
|
|
10934
|
+
const fiveMinutesFromNow = new Date(now.getTime() + 5 * 60 * 1e3);
|
|
10935
|
+
if (expiryTime < fiveMinutesFromNow) {
|
|
10936
|
+
const { accessToken, expiresIn } = await refreshGoogleCalendarTokenUtil(
|
|
10937
|
+
syncedCalendar.refreshToken
|
|
10938
|
+
);
|
|
10939
|
+
const tokenExpiry = /* @__PURE__ */ new Date();
|
|
10940
|
+
tokenExpiry.setSeconds(tokenExpiry.getSeconds() + expiresIn);
|
|
10941
|
+
const updateData = {
|
|
10942
|
+
accessToken,
|
|
10943
|
+
tokenExpiry: Timestamp25.fromDate(tokenExpiry)
|
|
10944
|
+
};
|
|
10945
|
+
switch (entityType) {
|
|
10946
|
+
case "practitioner":
|
|
10947
|
+
await updatePractitionerSyncedCalendarUtil(
|
|
10948
|
+
db,
|
|
10949
|
+
entityId,
|
|
10950
|
+
syncedCalendar.id,
|
|
10951
|
+
updateData
|
|
10952
|
+
);
|
|
10953
|
+
break;
|
|
10954
|
+
case "patient":
|
|
10955
|
+
await updatePatientSyncedCalendarUtil(
|
|
10956
|
+
db,
|
|
10957
|
+
entityId,
|
|
10958
|
+
syncedCalendar.id,
|
|
10959
|
+
updateData
|
|
10960
|
+
);
|
|
10961
|
+
break;
|
|
10962
|
+
case "clinic":
|
|
10963
|
+
await updateClinicSyncedCalendarUtil(
|
|
10964
|
+
db,
|
|
10965
|
+
entityId,
|
|
10966
|
+
syncedCalendar.id,
|
|
10967
|
+
updateData
|
|
10968
|
+
);
|
|
10969
|
+
break;
|
|
10970
|
+
}
|
|
10971
|
+
return accessToken;
|
|
10972
|
+
}
|
|
10973
|
+
return syncedCalendar.accessToken;
|
|
10974
|
+
}
|
|
10975
|
+
async function syncEventsToGoogleCalendarUtil(db, entityType, entityId, syncedCalendar, events, existingSyncId) {
|
|
10976
|
+
var _a, _b;
|
|
10977
|
+
try {
|
|
10978
|
+
const { accessToken } = await refreshGoogleCalendarTokenUtil(
|
|
10979
|
+
syncedCalendar.refreshToken
|
|
10980
|
+
);
|
|
10981
|
+
let syncedCount = 0;
|
|
10982
|
+
const errors = [];
|
|
10983
|
+
const eventIds = [];
|
|
10984
|
+
for (const event of events) {
|
|
10985
|
+
try {
|
|
10986
|
+
if (event.syncStatus === "external" /* EXTERNAL */) {
|
|
10987
|
+
continue;
|
|
10988
|
+
}
|
|
10989
|
+
if (entityType === "practitioner" && event.status !== "confirmed" /* CONFIRMED */) {
|
|
10990
|
+
continue;
|
|
10991
|
+
}
|
|
10992
|
+
if (entityType === "patient" && (event.status === "canceled" /* CANCELED */ || event.status === "rejected" /* REJECTED */)) {
|
|
10993
|
+
continue;
|
|
10994
|
+
}
|
|
10995
|
+
if (entityType === "clinic") {
|
|
10996
|
+
continue;
|
|
10997
|
+
}
|
|
10998
|
+
const googleEvent = convertCalendarEventToGoogleEventUtil(event);
|
|
10999
|
+
const headers = {
|
|
11000
|
+
Authorization: `Bearer ${accessToken}`,
|
|
11001
|
+
"Content-Type": "application/json"
|
|
11002
|
+
};
|
|
11003
|
+
let responseId = "";
|
|
11004
|
+
if (existingSyncId) {
|
|
11005
|
+
const response = await makeRequest(
|
|
11006
|
+
"put",
|
|
11007
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${existingSyncId}`,
|
|
11008
|
+
headers,
|
|
11009
|
+
googleEvent
|
|
11010
|
+
);
|
|
11011
|
+
responseId = response.id;
|
|
11012
|
+
} else {
|
|
11013
|
+
const existingSync = (_a = event.syncedCalendarEventId) == null ? void 0 : _a.find(
|
|
11014
|
+
(sync) => sync.syncedCalendarProvider === "google" /* GOOGLE */ && // We should check if this is the same calendar we're syncing with, but that information isn't stored
|
|
11015
|
+
// For now, we'll just use the first Google Calendar sync ID
|
|
11016
|
+
sync.syncedCalendarProvider === syncedCalendar.provider
|
|
11017
|
+
);
|
|
11018
|
+
if (existingSync) {
|
|
11019
|
+
const response = await makeRequest(
|
|
11020
|
+
"put",
|
|
11021
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${existingSync.eventId}`,
|
|
11022
|
+
headers,
|
|
11023
|
+
googleEvent
|
|
11024
|
+
);
|
|
11025
|
+
responseId = response.id;
|
|
11026
|
+
} else {
|
|
11027
|
+
const response = await makeRequest(
|
|
11028
|
+
"post",
|
|
11029
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events`,
|
|
11030
|
+
headers,
|
|
11031
|
+
googleEvent
|
|
11032
|
+
);
|
|
11033
|
+
responseId = response.id;
|
|
11034
|
+
}
|
|
11035
|
+
}
|
|
11036
|
+
if (responseId) {
|
|
11037
|
+
eventIds.push(responseId);
|
|
11038
|
+
syncedCount++;
|
|
11039
|
+
}
|
|
11040
|
+
} catch (error) {
|
|
11041
|
+
const apiError = error;
|
|
11042
|
+
errors.push({
|
|
11043
|
+
eventId: event.id,
|
|
11044
|
+
error: apiError.message || "Unknown error",
|
|
11045
|
+
status: (_b = apiError.response) == null ? void 0 : _b.status
|
|
11046
|
+
});
|
|
11047
|
+
}
|
|
11048
|
+
}
|
|
11049
|
+
await updateLastSyncedTimestampUtil(
|
|
11050
|
+
db,
|
|
11051
|
+
entityType,
|
|
11052
|
+
entityId,
|
|
11053
|
+
syncedCalendar.id
|
|
11054
|
+
);
|
|
11055
|
+
return {
|
|
11056
|
+
success: errors.length === 0,
|
|
11057
|
+
syncedEvents: syncedCount,
|
|
11058
|
+
errors,
|
|
11059
|
+
eventIds
|
|
11060
|
+
};
|
|
11061
|
+
} catch (error) {
|
|
11062
|
+
console.error("Error syncing with Google Calendar:", error);
|
|
11063
|
+
return {
|
|
11064
|
+
success: false,
|
|
11065
|
+
syncedEvents: 0,
|
|
11066
|
+
errors: [{ error: error.message || "Unknown error" }],
|
|
11067
|
+
eventIds: []
|
|
11068
|
+
};
|
|
11069
|
+
}
|
|
11070
|
+
}
|
|
11071
|
+
async function fetchEventsFromGoogleCalendarUtil(db, entityType, entityId, syncedCalendar, startDate, endDate) {
|
|
11072
|
+
try {
|
|
11073
|
+
const accessToken = await ensureValidToken(
|
|
11074
|
+
db,
|
|
11075
|
+
entityType,
|
|
11076
|
+
entityId,
|
|
11077
|
+
syncedCalendar
|
|
11078
|
+
);
|
|
11079
|
+
const timeMin = startDate.toISOString();
|
|
11080
|
+
const timeMax = endDate.toISOString();
|
|
11081
|
+
const response = await makeRequest(
|
|
11082
|
+
"get",
|
|
11083
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events`,
|
|
11084
|
+
{ Authorization: `Bearer ${accessToken}` },
|
|
11085
|
+
void 0,
|
|
11086
|
+
{
|
|
11087
|
+
timeMin,
|
|
11088
|
+
timeMax,
|
|
11089
|
+
singleEvents: "true",
|
|
11090
|
+
orderBy: "startTime"
|
|
11091
|
+
}
|
|
11092
|
+
);
|
|
11093
|
+
await updateLastSyncedTimestampUtil(
|
|
11094
|
+
db,
|
|
11095
|
+
entityType,
|
|
11096
|
+
entityId,
|
|
11097
|
+
syncedCalendar.id
|
|
11098
|
+
);
|
|
11099
|
+
return response.items;
|
|
11100
|
+
} catch (error) {
|
|
11101
|
+
const apiError = error;
|
|
11102
|
+
console.error(
|
|
11103
|
+
"Error fetching events from Google Calendar:",
|
|
11104
|
+
apiError.message || "Unknown error"
|
|
11105
|
+
);
|
|
11106
|
+
throw new Error(
|
|
11107
|
+
`Failed to fetch events from Google Calendar: ${apiError.message || "Unknown error"}`
|
|
11108
|
+
);
|
|
11109
|
+
}
|
|
11110
|
+
}
|
|
11111
|
+
function convertGoogleEventToCalendarEventUtil(googleEvent, entityId, entityType) {
|
|
11112
|
+
const start = googleEvent.start.dateTime ? new Date(googleEvent.start.dateTime) : new Date(googleEvent.start.date);
|
|
11113
|
+
const end = googleEvent.end.dateTime ? new Date(googleEvent.end.dateTime) : new Date(googleEvent.end.date);
|
|
11114
|
+
const calendarEvent = {
|
|
11115
|
+
eventName: googleEvent.summary || "External Event",
|
|
11116
|
+
eventLocation: googleEvent.location,
|
|
11117
|
+
eventTime: {
|
|
11118
|
+
start: Timestamp25.fromDate(start),
|
|
11119
|
+
end: Timestamp25.fromDate(end)
|
|
11120
|
+
},
|
|
11121
|
+
description: googleEvent.description || "",
|
|
11122
|
+
// External events are always set as CONFIRMED - status updates will happen externally
|
|
11123
|
+
status: "confirmed" /* CONFIRMED */,
|
|
11124
|
+
// All external events are marked as EXTERNAL to indicate they originated outside our system
|
|
11125
|
+
syncStatus: "external" /* EXTERNAL */,
|
|
11126
|
+
// All external events are treated as BLOCKING events
|
|
11127
|
+
eventType: "blocking" /* BLOCKING */,
|
|
11128
|
+
// Store the original Google Calendar event ID
|
|
11129
|
+
syncedCalendarEventId: [
|
|
11130
|
+
{
|
|
11131
|
+
eventId: googleEvent.id,
|
|
11132
|
+
syncedCalendarProvider: "google" /* GOOGLE */,
|
|
11133
|
+
syncedAt: Timestamp25.now()
|
|
11134
|
+
}
|
|
11135
|
+
]
|
|
11136
|
+
};
|
|
11137
|
+
switch (entityType) {
|
|
11138
|
+
case "practitioner":
|
|
11139
|
+
calendarEvent.practitionerProfileId = entityId;
|
|
11140
|
+
break;
|
|
11141
|
+
case "patient":
|
|
11142
|
+
calendarEvent.patientProfileId = entityId;
|
|
11143
|
+
break;
|
|
11144
|
+
case "clinic":
|
|
11145
|
+
calendarEvent.clinicBranchId = entityId;
|
|
11146
|
+
break;
|
|
11147
|
+
}
|
|
11148
|
+
return calendarEvent;
|
|
11149
|
+
}
|
|
11150
|
+
function convertCalendarEventToGoogleEventUtil(calendarEvent) {
|
|
11151
|
+
const googleEvent = {
|
|
11152
|
+
summary: calendarEvent.eventName,
|
|
11153
|
+
location: calendarEvent.eventLocation,
|
|
11154
|
+
description: calendarEvent.description,
|
|
11155
|
+
start: {
|
|
11156
|
+
dateTime: calendarEvent.eventTime.start.toDate().toISOString(),
|
|
11157
|
+
timeZone: "UTC"
|
|
11158
|
+
},
|
|
11159
|
+
end: {
|
|
11160
|
+
dateTime: calendarEvent.eventTime.end.toDate().toISOString(),
|
|
11161
|
+
timeZone: "UTC"
|
|
11162
|
+
},
|
|
11163
|
+
// Add reminders
|
|
11164
|
+
reminders: {
|
|
11165
|
+
useDefault: false,
|
|
11166
|
+
overrides: [
|
|
11167
|
+
{ method: "email", minutes: 24 * 60 },
|
|
11168
|
+
// 1 day before
|
|
11169
|
+
{ method: "popup", minutes: 30 }
|
|
11170
|
+
// 30 minutes before
|
|
11171
|
+
]
|
|
11172
|
+
}
|
|
11173
|
+
};
|
|
11174
|
+
switch (calendarEvent.status) {
|
|
11175
|
+
case "confirmed" /* CONFIRMED */:
|
|
11176
|
+
googleEvent.status = "confirmed";
|
|
11177
|
+
break;
|
|
11178
|
+
case "canceled" /* CANCELED */:
|
|
11179
|
+
googleEvent.status = "cancelled";
|
|
11180
|
+
break;
|
|
11181
|
+
case "pending" /* PENDING */:
|
|
11182
|
+
googleEvent.status = "tentative";
|
|
11183
|
+
break;
|
|
11184
|
+
default:
|
|
11185
|
+
googleEvent.status = "confirmed";
|
|
11186
|
+
}
|
|
11187
|
+
if (calendarEvent.eventType === "appointment" /* APPOINTMENT */) {
|
|
11188
|
+
googleEvent.attendees = [];
|
|
11189
|
+
if (calendarEvent.practitionerProfileId) {
|
|
11190
|
+
googleEvent.attendees.push({
|
|
11191
|
+
email: "practitioner@example.com",
|
|
11192
|
+
// This would be fetched from the practitioner profile
|
|
11193
|
+
displayName: "Dr. Practitioner",
|
|
11194
|
+
// This would be fetched from the practitioner profile
|
|
11195
|
+
responseStatus: "accepted"
|
|
11196
|
+
});
|
|
11197
|
+
}
|
|
11198
|
+
if (calendarEvent.patientProfileId) {
|
|
11199
|
+
googleEvent.attendees.push({
|
|
11200
|
+
email: "patient@example.com",
|
|
11201
|
+
// This would be fetched from the patient profile
|
|
11202
|
+
displayName: "Patient",
|
|
11203
|
+
// This would be fetched from the patient profile
|
|
11204
|
+
responseStatus: "needsAction"
|
|
11205
|
+
});
|
|
11206
|
+
}
|
|
11207
|
+
}
|
|
11208
|
+
return googleEvent;
|
|
11209
|
+
}
|
|
11210
|
+
async function deleteGoogleCalendarEventUtil(db, entityType, entityId, syncedCalendar, eventId) {
|
|
11211
|
+
try {
|
|
11212
|
+
const accessToken = await ensureValidToken(
|
|
11213
|
+
db,
|
|
11214
|
+
entityType,
|
|
11215
|
+
entityId,
|
|
11216
|
+
syncedCalendar
|
|
11217
|
+
);
|
|
11218
|
+
await makeRequest(
|
|
11219
|
+
"delete",
|
|
11220
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${eventId}`,
|
|
11221
|
+
{ Authorization: `Bearer ${accessToken}` }
|
|
11222
|
+
);
|
|
11223
|
+
return true;
|
|
11224
|
+
} catch (error) {
|
|
11225
|
+
const apiError = error;
|
|
11226
|
+
console.error(
|
|
11227
|
+
"Error deleting event from Google Calendar:",
|
|
11228
|
+
apiError.message || "Unknown error"
|
|
11229
|
+
);
|
|
11230
|
+
throw new Error(
|
|
11231
|
+
`Failed to delete event from Google Calendar: ${apiError.message || "Unknown error"}`
|
|
11232
|
+
);
|
|
11233
|
+
}
|
|
11234
|
+
}
|
|
11235
|
+
function getGoogleCalendarOAuthUrlUtil(scopes = ["https://www.googleapis.com/auth/calendar"]) {
|
|
11236
|
+
const scopeString = encodeURIComponent(scopes.join(" "));
|
|
11237
|
+
return `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(
|
|
11238
|
+
REDIRECT_URI
|
|
11239
|
+
)}&response_type=code&scope=${scopeString}&access_type=offline&prompt=consent`;
|
|
11240
|
+
}
|
|
11241
|
+
|
|
11242
|
+
// src/services/calendar/synced-calendars.service.ts
|
|
11243
|
+
var SyncedCalendarsService = class extends BaseService {
|
|
11244
|
+
/**
|
|
11245
|
+
* Creates a new SyncedCalendarsService instance
|
|
11246
|
+
* @param db - Firestore instance
|
|
11247
|
+
* @param auth - Firebase Auth instance
|
|
11248
|
+
* @param app - Firebase App instance
|
|
11249
|
+
*/
|
|
11250
|
+
constructor(db, auth, app) {
|
|
11251
|
+
super(db, auth, app);
|
|
11252
|
+
}
|
|
11253
|
+
// ===== Practitioner Synced Calendars =====
|
|
11254
|
+
/**
|
|
11255
|
+
* Creates a synced calendar for a practitioner
|
|
11256
|
+
* @param practitionerId - ID of the practitioner
|
|
11257
|
+
* @param calendarData - Synced calendar data
|
|
11258
|
+
* @returns Created synced calendar
|
|
11259
|
+
*/
|
|
11260
|
+
async createPractitionerSyncedCalendar(practitionerId, calendarData) {
|
|
11261
|
+
return createPractitionerSyncedCalendarUtil(
|
|
11262
|
+
this.db,
|
|
11263
|
+
practitionerId,
|
|
11264
|
+
calendarData,
|
|
11265
|
+
this.generateId.bind(this)
|
|
11266
|
+
);
|
|
11267
|
+
}
|
|
11268
|
+
/**
|
|
11269
|
+
* Gets a synced calendar for a practitioner
|
|
11270
|
+
* @param practitionerId - ID of the practitioner
|
|
11271
|
+
* @param calendarId - ID of the synced calendar
|
|
11272
|
+
* @returns Synced calendar or null if not found
|
|
11273
|
+
*/
|
|
11274
|
+
async getPractitionerSyncedCalendar(practitionerId, calendarId) {
|
|
11275
|
+
return getPractitionerSyncedCalendarUtil(
|
|
11276
|
+
this.db,
|
|
11277
|
+
practitionerId,
|
|
11278
|
+
calendarId
|
|
11279
|
+
);
|
|
11280
|
+
}
|
|
11281
|
+
/**
|
|
11282
|
+
* Gets all synced calendars for a practitioner
|
|
11283
|
+
* @param practitionerId - ID of the practitioner
|
|
11284
|
+
* @returns Array of synced calendars
|
|
11285
|
+
*/
|
|
11286
|
+
async getPractitionerSyncedCalendars(practitionerId) {
|
|
11287
|
+
return getPractitionerSyncedCalendarsUtil(this.db, practitionerId);
|
|
11288
|
+
}
|
|
11289
|
+
/**
|
|
11290
|
+
* Updates a synced calendar for a practitioner
|
|
11291
|
+
* @param practitionerId - ID of the practitioner
|
|
11292
|
+
* @param calendarId - ID of the synced calendar
|
|
11293
|
+
* @param updateData - Data to update
|
|
11294
|
+
* @returns Updated synced calendar
|
|
11295
|
+
*/
|
|
11296
|
+
async updatePractitionerSyncedCalendar(practitionerId, calendarId, updateData) {
|
|
11297
|
+
return updatePractitionerSyncedCalendarUtil(
|
|
11298
|
+
this.db,
|
|
11299
|
+
practitionerId,
|
|
11300
|
+
calendarId,
|
|
11301
|
+
updateData
|
|
11302
|
+
);
|
|
11303
|
+
}
|
|
11304
|
+
/**
|
|
11305
|
+
* Deletes a synced calendar for a practitioner
|
|
11306
|
+
* @param practitionerId - ID of the practitioner
|
|
11307
|
+
* @param calendarId - ID of the synced calendar
|
|
11308
|
+
*/
|
|
11309
|
+
async deletePractitionerSyncedCalendar(practitionerId, calendarId) {
|
|
11310
|
+
return deletePractitionerSyncedCalendarUtil(
|
|
11311
|
+
this.db,
|
|
11312
|
+
practitionerId,
|
|
11313
|
+
calendarId
|
|
11314
|
+
);
|
|
11315
|
+
}
|
|
11316
|
+
// ===== Patient Synced Calendars =====
|
|
11317
|
+
/**
|
|
11318
|
+
* Creates a synced calendar for a patient
|
|
11319
|
+
* @param patientId - ID of the patient
|
|
11320
|
+
* @param calendarData - Synced calendar data
|
|
11321
|
+
* @returns Created synced calendar
|
|
11322
|
+
*/
|
|
11323
|
+
async createPatientSyncedCalendar(patientId, calendarData) {
|
|
11324
|
+
return createPatientSyncedCalendarUtil(
|
|
11325
|
+
this.db,
|
|
11326
|
+
patientId,
|
|
11327
|
+
calendarData,
|
|
11328
|
+
this.generateId.bind(this)
|
|
11329
|
+
);
|
|
11330
|
+
}
|
|
11331
|
+
/**
|
|
11332
|
+
* Gets a synced calendar for a patient
|
|
11333
|
+
* @param patientId - ID of the patient
|
|
11334
|
+
* @param calendarId - ID of the synced calendar
|
|
11335
|
+
* @returns Synced calendar or null if not found
|
|
11336
|
+
*/
|
|
11337
|
+
async getPatientSyncedCalendar(patientId, calendarId) {
|
|
11338
|
+
return getPatientSyncedCalendarUtil(this.db, patientId, calendarId);
|
|
11339
|
+
}
|
|
11340
|
+
/**
|
|
11341
|
+
* Gets all synced calendars for a patient
|
|
11342
|
+
* @param patientId - ID of the patient
|
|
11343
|
+
* @returns Array of synced calendars
|
|
11344
|
+
*/
|
|
11345
|
+
async getPatientSyncedCalendars(patientId) {
|
|
11346
|
+
return getPatientSyncedCalendarsUtil(this.db, patientId);
|
|
11347
|
+
}
|
|
11348
|
+
/**
|
|
11349
|
+
* Updates a synced calendar for a patient
|
|
11350
|
+
* @param patientId - ID of the patient
|
|
11351
|
+
* @param calendarId - ID of the synced calendar
|
|
11352
|
+
* @param updateData - Data to update
|
|
11353
|
+
* @returns Updated synced calendar
|
|
11354
|
+
*/
|
|
11355
|
+
async updatePatientSyncedCalendar(patientId, calendarId, updateData) {
|
|
11356
|
+
return updatePatientSyncedCalendarUtil(
|
|
11357
|
+
this.db,
|
|
11358
|
+
patientId,
|
|
11359
|
+
calendarId,
|
|
11360
|
+
updateData
|
|
11361
|
+
);
|
|
11362
|
+
}
|
|
11363
|
+
/**
|
|
11364
|
+
* Deletes a synced calendar for a patient
|
|
11365
|
+
* @param patientId - ID of the patient
|
|
11366
|
+
* @param calendarId - ID of the synced calendar
|
|
11367
|
+
*/
|
|
11368
|
+
async deletePatientSyncedCalendar(patientId, calendarId) {
|
|
11369
|
+
return deletePatientSyncedCalendarUtil(this.db, patientId, calendarId);
|
|
11370
|
+
}
|
|
11371
|
+
// ===== Clinic Synced Calendars =====
|
|
11372
|
+
/**
|
|
11373
|
+
* Creates a synced calendar for a clinic
|
|
11374
|
+
* @param clinicId - ID of the clinic
|
|
11375
|
+
* @param calendarData - Synced calendar data
|
|
11376
|
+
* @returns Created synced calendar
|
|
11377
|
+
*/
|
|
11378
|
+
async createClinicSyncedCalendar(clinicId, calendarData) {
|
|
11379
|
+
return createClinicSyncedCalendarUtil(
|
|
11380
|
+
this.db,
|
|
11381
|
+
clinicId,
|
|
11382
|
+
calendarData,
|
|
11383
|
+
this.generateId.bind(this)
|
|
11384
|
+
);
|
|
11385
|
+
}
|
|
11386
|
+
/**
|
|
11387
|
+
* Gets a synced calendar for a clinic
|
|
11388
|
+
* @param clinicId - ID of the clinic
|
|
11389
|
+
* @param calendarId - ID of the synced calendar
|
|
11390
|
+
* @returns Synced calendar or null if not found
|
|
11391
|
+
*/
|
|
11392
|
+
async getClinicSyncedCalendar(clinicId, calendarId) {
|
|
11393
|
+
return getClinicSyncedCalendarUtil(this.db, clinicId, calendarId);
|
|
11394
|
+
}
|
|
11395
|
+
/**
|
|
11396
|
+
* Gets all synced calendars for a clinic
|
|
11397
|
+
* @param clinicId - ID of the clinic
|
|
11398
|
+
* @returns Array of synced calendars
|
|
11399
|
+
*/
|
|
11400
|
+
async getClinicSyncedCalendars(clinicId) {
|
|
11401
|
+
return getClinicSyncedCalendarsUtil(this.db, clinicId);
|
|
11402
|
+
}
|
|
11403
|
+
/**
|
|
11404
|
+
* Updates a synced calendar for a clinic
|
|
11405
|
+
* @param clinicId - ID of the clinic
|
|
11406
|
+
* @param calendarId - ID of the synced calendar
|
|
11407
|
+
* @param updateData - Data to update
|
|
11408
|
+
* @returns Updated synced calendar
|
|
11409
|
+
*/
|
|
11410
|
+
async updateClinicSyncedCalendar(clinicId, calendarId, updateData) {
|
|
11411
|
+
return updateClinicSyncedCalendarUtil(
|
|
11412
|
+
this.db,
|
|
11413
|
+
clinicId,
|
|
11414
|
+
calendarId,
|
|
11415
|
+
updateData
|
|
11416
|
+
);
|
|
11417
|
+
}
|
|
11418
|
+
/**
|
|
11419
|
+
* Deletes a synced calendar for a clinic
|
|
11420
|
+
* @param clinicId - ID of the clinic
|
|
11421
|
+
* @param calendarId - ID of the synced calendar
|
|
11422
|
+
*/
|
|
11423
|
+
async deleteClinicSyncedCalendar(clinicId, calendarId) {
|
|
11424
|
+
return deleteClinicSyncedCalendarUtil(this.db, clinicId, calendarId);
|
|
11425
|
+
}
|
|
11426
|
+
// ===== Google Calendar Integration =====
|
|
11427
|
+
/**
|
|
11428
|
+
* Gets the OAuth URL for Google Calendar
|
|
11429
|
+
* @param scopes - OAuth scopes to request
|
|
11430
|
+
* @returns OAuth URL
|
|
11431
|
+
*/
|
|
11432
|
+
getGoogleCalendarOAuthUrl(scopes = ["https://www.googleapis.com/auth/calendar"]) {
|
|
11433
|
+
return getGoogleCalendarOAuthUrlUtil(scopes);
|
|
11434
|
+
}
|
|
11435
|
+
/**
|
|
11436
|
+
* Authenticates with Google Calendar using an authorization code
|
|
11437
|
+
* @param authCode - Authorization code from Google OAuth
|
|
11438
|
+
* @returns Access token, refresh token, and expiration time
|
|
11439
|
+
*/
|
|
11440
|
+
async authenticateWithGoogleCalendar(authCode) {
|
|
11441
|
+
return authenticateWithGoogleCalendarUtil(authCode);
|
|
11442
|
+
}
|
|
11443
|
+
/**
|
|
11444
|
+
* Lists available Google Calendars for a user
|
|
11445
|
+
* @param accessToken - Google API access token
|
|
11446
|
+
* @returns List of available calendars
|
|
11447
|
+
*/
|
|
11448
|
+
async listGoogleCalendars(accessToken) {
|
|
11449
|
+
return listGoogleCalendarsUtil(accessToken);
|
|
11450
|
+
}
|
|
11451
|
+
/**
|
|
11452
|
+
* Syncs events from our system to Google Calendar for a practitioner
|
|
11453
|
+
* @param practitionerId - ID of the practitioner
|
|
11454
|
+
* @param calendarId - ID of the synced calendar
|
|
11455
|
+
* @param events - Events to sync
|
|
11456
|
+
* @param existingSyncId - Optional existing sync ID for updating an event
|
|
11457
|
+
* @returns Result of the sync operation
|
|
11458
|
+
*/
|
|
11459
|
+
async syncPractitionerEventsToGoogleCalendar(practitionerId, calendarId, events, existingSyncId) {
|
|
11460
|
+
const syncedCalendar = await this.getPractitionerSyncedCalendar(
|
|
11461
|
+
practitionerId,
|
|
11462
|
+
calendarId
|
|
11463
|
+
);
|
|
11464
|
+
if (!syncedCalendar) {
|
|
11465
|
+
throw new Error("Synced calendar not found");
|
|
11466
|
+
}
|
|
11467
|
+
return syncEventsToGoogleCalendarUtil(
|
|
11468
|
+
this.db,
|
|
11469
|
+
"practitioner",
|
|
11470
|
+
practitionerId,
|
|
11471
|
+
syncedCalendar,
|
|
11472
|
+
events,
|
|
11473
|
+
existingSyncId
|
|
11474
|
+
);
|
|
11475
|
+
}
|
|
11476
|
+
/**
|
|
11477
|
+
* Syncs events from our system to Google Calendar for a patient
|
|
11478
|
+
* @param patientId - ID of the patient
|
|
11479
|
+
* @param calendarId - ID of the synced calendar
|
|
11480
|
+
* @param events - Events to sync
|
|
11481
|
+
* @param existingSyncId - Optional existing sync ID for updating an event
|
|
11482
|
+
* @returns Result of the sync operation
|
|
11483
|
+
*/
|
|
11484
|
+
async syncPatientEventsToGoogleCalendar(patientId, calendarId, events, existingSyncId) {
|
|
11485
|
+
const syncedCalendar = await this.getPatientSyncedCalendar(
|
|
11486
|
+
patientId,
|
|
11487
|
+
calendarId
|
|
11488
|
+
);
|
|
11489
|
+
if (!syncedCalendar) {
|
|
11490
|
+
throw new Error("Synced calendar not found");
|
|
11491
|
+
}
|
|
11492
|
+
return syncEventsToGoogleCalendarUtil(
|
|
11493
|
+
this.db,
|
|
11494
|
+
"patient",
|
|
11495
|
+
patientId,
|
|
11496
|
+
syncedCalendar,
|
|
11497
|
+
events,
|
|
11498
|
+
existingSyncId
|
|
11499
|
+
);
|
|
11500
|
+
}
|
|
11501
|
+
/**
|
|
11502
|
+
* Syncs events from our system to Google Calendar for a clinic
|
|
11503
|
+
* @param clinicId - ID of the clinic
|
|
11504
|
+
* @param calendarId - ID of the synced calendar
|
|
11505
|
+
* @param events - Events to sync
|
|
11506
|
+
* @returns Result of the sync operation
|
|
11507
|
+
*/
|
|
11508
|
+
async syncClinicEventsToGoogleCalendar(clinicId, calendarId, events) {
|
|
11509
|
+
const syncedCalendar = await this.getClinicSyncedCalendar(
|
|
11510
|
+
clinicId,
|
|
11511
|
+
calendarId
|
|
11512
|
+
);
|
|
11513
|
+
if (!syncedCalendar) {
|
|
11514
|
+
throw new Error("Synced calendar not found");
|
|
11515
|
+
}
|
|
11516
|
+
return syncEventsToGoogleCalendarUtil(
|
|
11517
|
+
this.db,
|
|
11518
|
+
"clinic",
|
|
11519
|
+
clinicId,
|
|
11520
|
+
syncedCalendar,
|
|
11521
|
+
events
|
|
11522
|
+
);
|
|
11523
|
+
}
|
|
11524
|
+
/**
|
|
11525
|
+
* Fetches events from Google Calendar for a practitioner
|
|
11526
|
+
* @param practitionerId - ID of the practitioner
|
|
11527
|
+
* @param calendarId - ID of the synced calendar
|
|
11528
|
+
* @param startDate - Start date for fetching events
|
|
11529
|
+
* @param endDate - End date for fetching events
|
|
11530
|
+
* @returns Events fetched from Google Calendar
|
|
11531
|
+
*/
|
|
11532
|
+
async fetchEventsFromPractitionerGoogleCalendar(practitionerId, calendarId, startDate, endDate) {
|
|
11533
|
+
const syncedCalendar = await this.getPractitionerSyncedCalendar(
|
|
11534
|
+
practitionerId,
|
|
11535
|
+
calendarId
|
|
11536
|
+
);
|
|
11537
|
+
if (!syncedCalendar) {
|
|
11538
|
+
throw new Error("Synced calendar not found");
|
|
11539
|
+
}
|
|
11540
|
+
return fetchEventsFromGoogleCalendarUtil(
|
|
11541
|
+
this.db,
|
|
11542
|
+
"practitioner",
|
|
11543
|
+
practitionerId,
|
|
11544
|
+
syncedCalendar,
|
|
11545
|
+
startDate,
|
|
11546
|
+
endDate
|
|
11547
|
+
);
|
|
11548
|
+
}
|
|
11549
|
+
/**
|
|
11550
|
+
* Fetches events from Google Calendar for a patient
|
|
11551
|
+
* @param patientId - ID of the patient
|
|
11552
|
+
* @param calendarId - ID of the synced calendar
|
|
11553
|
+
* @param startDate - Start date for fetching events
|
|
11554
|
+
* @param endDate - End date for fetching events
|
|
11555
|
+
* @returns Events fetched from Google Calendar
|
|
11556
|
+
*/
|
|
11557
|
+
async fetchEventsFromPatientGoogleCalendar(patientId, calendarId, startDate, endDate) {
|
|
11558
|
+
const syncedCalendar = await this.getPatientSyncedCalendar(
|
|
11559
|
+
patientId,
|
|
11560
|
+
calendarId
|
|
11561
|
+
);
|
|
11562
|
+
if (!syncedCalendar) {
|
|
11563
|
+
throw new Error("Synced calendar not found");
|
|
11564
|
+
}
|
|
11565
|
+
return fetchEventsFromGoogleCalendarUtil(
|
|
11566
|
+
this.db,
|
|
11567
|
+
"patient",
|
|
11568
|
+
patientId,
|
|
11569
|
+
syncedCalendar,
|
|
11570
|
+
startDate,
|
|
11571
|
+
endDate
|
|
11572
|
+
);
|
|
11573
|
+
}
|
|
11574
|
+
/**
|
|
11575
|
+
* Fetches events from Google Calendar for a clinic
|
|
11576
|
+
* @param clinicId - ID of the clinic
|
|
11577
|
+
* @param calendarId - ID of the synced calendar
|
|
11578
|
+
* @param startDate - Start date for fetching events
|
|
11579
|
+
* @param endDate - End date for fetching events
|
|
11580
|
+
* @returns Events fetched from Google Calendar
|
|
11581
|
+
*/
|
|
11582
|
+
async fetchEventsFromClinicGoogleCalendar(clinicId, calendarId, startDate, endDate) {
|
|
11583
|
+
const syncedCalendar = await this.getClinicSyncedCalendar(
|
|
11584
|
+
clinicId,
|
|
11585
|
+
calendarId
|
|
11586
|
+
);
|
|
11587
|
+
if (!syncedCalendar) {
|
|
11588
|
+
throw new Error("Synced calendar not found");
|
|
11589
|
+
}
|
|
11590
|
+
return fetchEventsFromGoogleCalendarUtil(
|
|
11591
|
+
this.db,
|
|
11592
|
+
"clinic",
|
|
11593
|
+
clinicId,
|
|
11594
|
+
syncedCalendar,
|
|
11595
|
+
startDate,
|
|
11596
|
+
endDate
|
|
11597
|
+
);
|
|
11598
|
+
}
|
|
11599
|
+
/**
|
|
11600
|
+
* Deletes an event from Google Calendar for a practitioner
|
|
11601
|
+
* @param practitionerId - ID of the practitioner
|
|
11602
|
+
* @param calendarId - ID of the synced calendar
|
|
11603
|
+
* @param eventId - ID of the event in Google Calendar
|
|
11604
|
+
* @returns Success status
|
|
11605
|
+
*/
|
|
11606
|
+
async deletePractitionerGoogleCalendarEvent(practitionerId, calendarId, eventId) {
|
|
11607
|
+
const syncedCalendar = await this.getPractitionerSyncedCalendar(
|
|
11608
|
+
practitionerId,
|
|
11609
|
+
calendarId
|
|
11610
|
+
);
|
|
11611
|
+
if (!syncedCalendar) {
|
|
11612
|
+
throw new Error("Synced calendar not found");
|
|
11613
|
+
}
|
|
11614
|
+
return deleteGoogleCalendarEventUtil(
|
|
11615
|
+
this.db,
|
|
11616
|
+
"practitioner",
|
|
11617
|
+
practitionerId,
|
|
11618
|
+
syncedCalendar,
|
|
11619
|
+
eventId
|
|
11620
|
+
);
|
|
11621
|
+
}
|
|
11622
|
+
/**
|
|
11623
|
+
* Deletes an event from Google Calendar for a patient
|
|
11624
|
+
* @param patientId - ID of the patient
|
|
11625
|
+
* @param calendarId - ID of the synced calendar
|
|
11626
|
+
* @param eventId - ID of the event in Google Calendar
|
|
11627
|
+
* @returns Success status
|
|
11628
|
+
*/
|
|
11629
|
+
async deletePatientGoogleCalendarEvent(patientId, calendarId, eventId) {
|
|
11630
|
+
const syncedCalendar = await this.getPatientSyncedCalendar(
|
|
11631
|
+
patientId,
|
|
11632
|
+
calendarId
|
|
11633
|
+
);
|
|
11634
|
+
if (!syncedCalendar) {
|
|
11635
|
+
throw new Error("Synced calendar not found");
|
|
11636
|
+
}
|
|
11637
|
+
return deleteGoogleCalendarEventUtil(
|
|
11638
|
+
this.db,
|
|
11639
|
+
"patient",
|
|
11640
|
+
patientId,
|
|
11641
|
+
syncedCalendar,
|
|
11642
|
+
eventId
|
|
11643
|
+
);
|
|
11644
|
+
}
|
|
11645
|
+
/**
|
|
11646
|
+
* Deletes an event from Google Calendar for a clinic
|
|
11647
|
+
* @param clinicId - ID of the clinic
|
|
11648
|
+
* @param calendarId - ID of the synced calendar
|
|
11649
|
+
* @param eventId - ID of the event in Google Calendar
|
|
11650
|
+
* @returns Success status
|
|
11651
|
+
*/
|
|
11652
|
+
async deleteClinicGoogleCalendarEvent(clinicId, calendarId, eventId) {
|
|
11653
|
+
const syncedCalendar = await this.getClinicSyncedCalendar(
|
|
11654
|
+
clinicId,
|
|
11655
|
+
calendarId
|
|
11656
|
+
);
|
|
11657
|
+
if (!syncedCalendar) {
|
|
11658
|
+
throw new Error("Synced calendar not found");
|
|
11659
|
+
}
|
|
11660
|
+
return deleteGoogleCalendarEventUtil(
|
|
11661
|
+
this.db,
|
|
11662
|
+
"clinic",
|
|
11663
|
+
clinicId,
|
|
11664
|
+
syncedCalendar,
|
|
11665
|
+
eventId
|
|
11666
|
+
);
|
|
11667
|
+
}
|
|
11668
|
+
/**
|
|
11669
|
+
* Converts Google Calendar events to our system's format for a practitioner
|
|
11670
|
+
* @param practitionerId - ID of the practitioner
|
|
11671
|
+
* @param googleEvents - Google Calendar events
|
|
11672
|
+
* @returns Converted calendar events
|
|
11673
|
+
*/
|
|
11674
|
+
convertGoogleEventsToPractitionerEvents(practitionerId, googleEvents) {
|
|
11675
|
+
return googleEvents.map(
|
|
11676
|
+
(event) => convertGoogleEventToCalendarEventUtil(
|
|
11677
|
+
event,
|
|
11678
|
+
practitionerId,
|
|
11679
|
+
"practitioner"
|
|
11680
|
+
)
|
|
11681
|
+
);
|
|
11682
|
+
}
|
|
11683
|
+
/**
|
|
11684
|
+
* Converts Google Calendar events to our system's format for a patient
|
|
11685
|
+
* @param patientId - ID of the patient
|
|
11686
|
+
* @param googleEvents - Google Calendar events
|
|
11687
|
+
* @returns Converted calendar events
|
|
11688
|
+
*/
|
|
11689
|
+
convertGoogleEventsToPatientEvents(patientId, googleEvents) {
|
|
11690
|
+
return googleEvents.map(
|
|
11691
|
+
(event) => convertGoogleEventToCalendarEventUtil(event, patientId, "patient")
|
|
11692
|
+
);
|
|
11693
|
+
}
|
|
11694
|
+
/**
|
|
11695
|
+
* Converts Google Calendar events to our system's format for a clinic
|
|
11696
|
+
* @param clinicId - ID of the clinic
|
|
11697
|
+
* @param googleEvents - Google Calendar events
|
|
11698
|
+
* @returns Converted calendar events
|
|
11699
|
+
*/
|
|
11700
|
+
convertGoogleEventsToClinicEvents(clinicId, googleEvents) {
|
|
11701
|
+
return googleEvents.map(
|
|
11702
|
+
(event) => convertGoogleEventToCalendarEventUtil(event, clinicId, "clinic")
|
|
11703
|
+
);
|
|
11704
|
+
}
|
|
11705
|
+
/**
|
|
11706
|
+
* Fetches a single event from Google Calendar for a practitioner
|
|
11707
|
+
* @param practitionerId - ID of the practitioner
|
|
11708
|
+
* @param calendarId - ID of the synced calendar
|
|
11709
|
+
* @param eventId - ID of the event in Google Calendar
|
|
11710
|
+
* @returns The event data or null if not found
|
|
11711
|
+
*/
|
|
11712
|
+
async fetchEventFromPractitionerGoogleCalendar(practitionerId, calendarId, eventId) {
|
|
11713
|
+
var _a;
|
|
11714
|
+
const syncedCalendar = await this.getPractitionerSyncedCalendar(
|
|
11715
|
+
practitionerId,
|
|
11716
|
+
calendarId
|
|
11717
|
+
);
|
|
11718
|
+
if (!syncedCalendar) {
|
|
11719
|
+
throw new Error("Synced calendar not found");
|
|
11720
|
+
}
|
|
11721
|
+
try {
|
|
11722
|
+
const { accessToken } = await refreshGoogleCalendarTokenUtil(
|
|
11723
|
+
syncedCalendar.refreshToken
|
|
11724
|
+
);
|
|
11725
|
+
const response = await makeRequest(
|
|
11726
|
+
"get",
|
|
11727
|
+
`${GOOGLE_CALENDAR_API_URL}/calendars/${syncedCalendar.calendarId}/events/${eventId}`,
|
|
11728
|
+
{ Authorization: `Bearer ${accessToken}` }
|
|
11729
|
+
);
|
|
11730
|
+
await updateLastSyncedTimestampUtil(
|
|
11731
|
+
this.db,
|
|
11732
|
+
"practitioner",
|
|
11733
|
+
practitionerId,
|
|
11734
|
+
syncedCalendar.id
|
|
11735
|
+
);
|
|
11736
|
+
return response;
|
|
11737
|
+
} catch (error) {
|
|
11738
|
+
if (((_a = error.response) == null ? void 0 : _a.status) === 404) {
|
|
11739
|
+
return null;
|
|
11740
|
+
}
|
|
11741
|
+
console.error(
|
|
11742
|
+
`Error fetching event from Google Calendar: ${error.message}`
|
|
11743
|
+
);
|
|
11744
|
+
throw error;
|
|
11745
|
+
}
|
|
11746
|
+
}
|
|
11747
|
+
};
|
|
11748
|
+
|
|
11749
|
+
// src/services/calendar/calendar.v2.service.ts
|
|
11750
|
+
var MIN_APPOINTMENT_DURATION = 15;
|
|
11751
|
+
var CalendarServiceV2 = class extends BaseService {
|
|
11752
|
+
/**
|
|
11753
|
+
* Creates a new CalendarService instance
|
|
11754
|
+
* @param db - Firestore instance
|
|
11755
|
+
* @param auth - Firebase Auth instance
|
|
11756
|
+
* @param app - Firebase App instance
|
|
11757
|
+
*/
|
|
11758
|
+
constructor(db, auth, app) {
|
|
11759
|
+
super(db, auth, app);
|
|
11760
|
+
this.syncedCalendarsService = new SyncedCalendarsService(db, auth, app);
|
|
11761
|
+
}
|
|
11762
|
+
// #region Public API Methods
|
|
11763
|
+
/**
|
|
11764
|
+
* Creates a new appointment with proper validation and scheduling rules
|
|
11765
|
+
* @param params - Appointment creation parameters
|
|
11766
|
+
* @returns Created calendar event
|
|
11767
|
+
*/
|
|
11768
|
+
async createAppointment(params) {
|
|
11769
|
+
await this.validateAppointmentParams(params);
|
|
11770
|
+
await this.validateClinicWorkingHours(params.clinicId, params.eventTime);
|
|
11771
|
+
await this.validateDoctorAvailability(
|
|
11772
|
+
params.doctorId,
|
|
11773
|
+
params.eventTime,
|
|
11774
|
+
params.clinicId
|
|
11775
|
+
);
|
|
11776
|
+
const { clinicInfo, practitionerInfo, patientInfo } = await this.fetchProfileInfoCards(
|
|
11777
|
+
params.clinicId,
|
|
11778
|
+
params.doctorId,
|
|
11779
|
+
params.patientId
|
|
11780
|
+
);
|
|
11781
|
+
const appointmentData = {
|
|
11782
|
+
clinicBranchId: params.clinicId,
|
|
11783
|
+
clinicBranchInfo: clinicInfo,
|
|
11784
|
+
practitionerProfileId: params.doctorId,
|
|
11785
|
+
practitionerProfileInfo: practitionerInfo,
|
|
11786
|
+
patientProfileId: params.patientId,
|
|
11787
|
+
patientProfileInfo: patientInfo,
|
|
11788
|
+
procedureId: params.procedureId,
|
|
11789
|
+
eventLocation: params.eventLocation,
|
|
11790
|
+
eventName: "Appointment",
|
|
11791
|
+
// TODO: Add procedure name when procedure model is available
|
|
11792
|
+
eventTime: params.eventTime,
|
|
11793
|
+
description: params.description || "",
|
|
11794
|
+
status: "pending" /* PENDING */,
|
|
11795
|
+
syncStatus: "internal" /* INTERNAL */,
|
|
11796
|
+
eventType: "appointment" /* APPOINTMENT */
|
|
11797
|
+
};
|
|
11798
|
+
const appointment = await createAppointmentUtil2(
|
|
11799
|
+
this.db,
|
|
11800
|
+
params.clinicId,
|
|
11801
|
+
params.doctorId,
|
|
11802
|
+
params.patientId,
|
|
11803
|
+
appointmentData,
|
|
11804
|
+
this.generateId.bind(this)
|
|
11805
|
+
);
|
|
11806
|
+
await this.syncAppointmentWithExternalCalendars(appointment);
|
|
11807
|
+
return appointment;
|
|
11808
|
+
}
|
|
11809
|
+
/**
|
|
11810
|
+
* Updates an existing appointment
|
|
11811
|
+
* @param params - Appointment update parameters
|
|
11812
|
+
* @returns Updated calendar event
|
|
11813
|
+
*/
|
|
11814
|
+
async updateAppointment(params) {
|
|
11815
|
+
await this.validateUpdatePermissions(params);
|
|
11816
|
+
const updateData = {
|
|
11817
|
+
eventTime: params.eventTime,
|
|
11818
|
+
description: params.description,
|
|
11819
|
+
status: params.status
|
|
11820
|
+
};
|
|
11821
|
+
const appointment = await updateAppointmentUtil2(
|
|
11822
|
+
this.db,
|
|
11823
|
+
params.clinicId,
|
|
11824
|
+
params.doctorId,
|
|
11825
|
+
params.patientId,
|
|
11826
|
+
params.appointmentId,
|
|
11827
|
+
updateData
|
|
11828
|
+
);
|
|
11829
|
+
await this.syncAppointmentWithExternalCalendars(appointment);
|
|
11830
|
+
return appointment;
|
|
11831
|
+
}
|
|
11832
|
+
/**
|
|
11833
|
+
* Gets available appointment slots for a doctor at a clinic
|
|
11834
|
+
* @param clinicId - ID of the clinic
|
|
11835
|
+
* @param doctorId - ID of the doctor
|
|
11836
|
+
* @param date - Date to check availability for
|
|
11837
|
+
* @returns Array of available time slots
|
|
11838
|
+
*/
|
|
11839
|
+
async getAvailableSlots(clinicId, doctorId, date) {
|
|
11840
|
+
const workingHours = await this.getClinicWorkingHours(clinicId, date);
|
|
11841
|
+
const doctorSchedule = await this.getDoctorSchedule(doctorId, date);
|
|
11842
|
+
const existingAppointments = await this.getDoctorAppointments(
|
|
11843
|
+
doctorId,
|
|
11844
|
+
date
|
|
11845
|
+
);
|
|
11846
|
+
return this.calculateAvailableSlots(
|
|
11847
|
+
workingHours,
|
|
11848
|
+
doctorSchedule,
|
|
11849
|
+
existingAppointments
|
|
11850
|
+
);
|
|
11851
|
+
}
|
|
11852
|
+
/**
|
|
11853
|
+
* Confirms an appointment
|
|
11854
|
+
* @param appointmentId - ID of the appointment
|
|
11855
|
+
* @param clinicId - ID of the clinic
|
|
11856
|
+
* @returns Confirmed calendar event
|
|
11857
|
+
*/
|
|
11858
|
+
async confirmAppointment(appointmentId, clinicId) {
|
|
11859
|
+
return this.updateAppointmentStatus(
|
|
11860
|
+
appointmentId,
|
|
11861
|
+
clinicId,
|
|
11862
|
+
"confirmed" /* CONFIRMED */
|
|
11863
|
+
);
|
|
11864
|
+
}
|
|
11865
|
+
/**
|
|
11866
|
+
* Rejects an appointment
|
|
11867
|
+
* @param appointmentId - ID of the appointment
|
|
11868
|
+
* @param clinicId - ID of the clinic
|
|
11869
|
+
* @returns Rejected calendar event
|
|
11870
|
+
*/
|
|
11871
|
+
async rejectAppointment(appointmentId, clinicId) {
|
|
11872
|
+
return this.updateAppointmentStatus(
|
|
11873
|
+
appointmentId,
|
|
11874
|
+
clinicId,
|
|
11875
|
+
"rejected" /* REJECTED */
|
|
11876
|
+
);
|
|
11877
|
+
}
|
|
11878
|
+
/**
|
|
11879
|
+
* Cancels an appointment
|
|
11880
|
+
* @param appointmentId - ID of the appointment
|
|
11881
|
+
* @param clinicId - ID of the clinic
|
|
11882
|
+
* @returns Canceled calendar event
|
|
11883
|
+
*/
|
|
11884
|
+
async cancelAppointment(appointmentId, clinicId) {
|
|
11885
|
+
return this.updateAppointmentStatus(
|
|
11886
|
+
appointmentId,
|
|
11887
|
+
clinicId,
|
|
11888
|
+
"canceled" /* CANCELED */
|
|
11889
|
+
);
|
|
11890
|
+
}
|
|
11891
|
+
/**
|
|
11892
|
+
* Imports events from external calendars
|
|
11893
|
+
* @param entityType - Type of entity (practitioner or patient)
|
|
11894
|
+
* @param entityId - ID of the entity
|
|
11895
|
+
* @param startDate - Start date for fetching events
|
|
11896
|
+
* @param endDate - End date for fetching events
|
|
11897
|
+
* @returns Number of events imported
|
|
11898
|
+
*/
|
|
11899
|
+
async importEventsFromExternalCalendars(entityType, entityId, startDate, endDate) {
|
|
11900
|
+
if (entityType === "patient") {
|
|
11901
|
+
return 0;
|
|
11902
|
+
}
|
|
11903
|
+
const syncedCalendars = await this.syncedCalendarsService.getPractitionerSyncedCalendars(
|
|
11904
|
+
entityId
|
|
11905
|
+
);
|
|
11906
|
+
const activeCalendars = syncedCalendars.filter((cal) => cal.isActive);
|
|
11907
|
+
if (activeCalendars.length === 0) {
|
|
11908
|
+
return 0;
|
|
11909
|
+
}
|
|
11910
|
+
let importedEventsCount = 0;
|
|
11911
|
+
const currentTime = Timestamp26.now();
|
|
11912
|
+
for (const calendar of activeCalendars) {
|
|
11913
|
+
try {
|
|
11914
|
+
let externalEvents = [];
|
|
11915
|
+
if (calendar.provider === "google" /* GOOGLE */) {
|
|
11916
|
+
externalEvents = await this.syncedCalendarsService.fetchEventsFromPractitionerGoogleCalendar(
|
|
11917
|
+
entityId,
|
|
11918
|
+
calendar.id,
|
|
11919
|
+
startDate,
|
|
11920
|
+
endDate
|
|
11921
|
+
);
|
|
11922
|
+
}
|
|
11923
|
+
for (const externalEvent of externalEvents) {
|
|
11924
|
+
try {
|
|
11925
|
+
const convertedEvent = this.syncedCalendarsService.convertGoogleEventsToPractitionerEvents(
|
|
11926
|
+
entityId,
|
|
11927
|
+
[externalEvent]
|
|
11928
|
+
)[0];
|
|
11929
|
+
if (!convertedEvent.eventTime) {
|
|
11930
|
+
continue;
|
|
11931
|
+
}
|
|
11932
|
+
const eventData = {
|
|
11933
|
+
// Ensure all required fields are set
|
|
11934
|
+
eventName: convertedEvent.eventName || "External Event",
|
|
11935
|
+
eventTime: convertedEvent.eventTime,
|
|
11936
|
+
description: convertedEvent.description || "",
|
|
11937
|
+
status: "confirmed" /* CONFIRMED */,
|
|
11938
|
+
syncStatus: "external" /* EXTERNAL */,
|
|
11939
|
+
eventType: "blocking" /* BLOCKING */,
|
|
11940
|
+
practitionerProfileId: entityId,
|
|
11941
|
+
syncedCalendarEventId: [
|
|
11942
|
+
{
|
|
11943
|
+
eventId: externalEvent.id,
|
|
11944
|
+
syncedCalendarProvider: calendar.provider,
|
|
11945
|
+
syncedAt: currentTime
|
|
11946
|
+
}
|
|
11947
|
+
]
|
|
11948
|
+
};
|
|
11949
|
+
const doctorEvent = await this.createDoctorBlockingEvent(
|
|
11950
|
+
entityId,
|
|
11951
|
+
eventData
|
|
11952
|
+
);
|
|
11953
|
+
if (doctorEvent) {
|
|
11954
|
+
importedEventsCount++;
|
|
11955
|
+
}
|
|
11956
|
+
} catch (eventError) {
|
|
11957
|
+
console.error("Error importing event:", eventError);
|
|
11958
|
+
}
|
|
11959
|
+
}
|
|
11960
|
+
} catch (calendarError) {
|
|
11961
|
+
console.error(
|
|
11962
|
+
`Error fetching events from calendar ${calendar.id}:`,
|
|
11963
|
+
calendarError
|
|
11964
|
+
);
|
|
11965
|
+
}
|
|
11966
|
+
}
|
|
11967
|
+
return importedEventsCount;
|
|
11968
|
+
}
|
|
11969
|
+
/**
|
|
11970
|
+
* Creates a blocking event in a doctor's calendar
|
|
11971
|
+
* @param doctorId - ID of the doctor
|
|
11972
|
+
* @param eventData - Calendar event data
|
|
11973
|
+
* @returns Created calendar event
|
|
11974
|
+
*/
|
|
11975
|
+
async createDoctorBlockingEvent(doctorId, eventData) {
|
|
11976
|
+
try {
|
|
11977
|
+
const eventId = this.generateId();
|
|
11978
|
+
const eventRef = doc23(
|
|
11979
|
+
this.db,
|
|
11980
|
+
PRACTITIONERS_COLLECTION,
|
|
11981
|
+
doctorId,
|
|
11982
|
+
CALENDAR_COLLECTION,
|
|
11983
|
+
eventId
|
|
11984
|
+
);
|
|
11985
|
+
const newEvent = {
|
|
11986
|
+
id: eventId,
|
|
11987
|
+
...eventData,
|
|
11988
|
+
createdAt: serverTimestamp20(),
|
|
11989
|
+
updatedAt: serverTimestamp20()
|
|
11990
|
+
};
|
|
11991
|
+
await setDoc21(eventRef, newEvent);
|
|
11992
|
+
return {
|
|
11993
|
+
...newEvent,
|
|
11994
|
+
createdAt: Timestamp26.now(),
|
|
11995
|
+
updatedAt: Timestamp26.now()
|
|
11996
|
+
};
|
|
11997
|
+
} catch (error) {
|
|
11998
|
+
console.error(
|
|
11999
|
+
`Error creating blocking event for doctor ${doctorId}:`,
|
|
12000
|
+
error
|
|
12001
|
+
);
|
|
12002
|
+
return null;
|
|
12003
|
+
}
|
|
12004
|
+
}
|
|
12005
|
+
/**
|
|
12006
|
+
* Periodically syncs events from external calendars for doctors
|
|
12007
|
+
* This would be called via a scheduled Cloud Function
|
|
12008
|
+
* @param lookbackDays - Number of days to look back for events
|
|
12009
|
+
* @param lookforwardDays - Number of days to look forward for events
|
|
12010
|
+
*/
|
|
12011
|
+
async synchronizeExternalCalendars(lookbackDays = 7, lookforwardDays = 30) {
|
|
12012
|
+
try {
|
|
12013
|
+
const practitionersRef = collection23(this.db, PRACTITIONERS_COLLECTION);
|
|
12014
|
+
const practitionersSnapshot = await getDocs23(practitionersRef);
|
|
12015
|
+
const startDate = /* @__PURE__ */ new Date();
|
|
12016
|
+
startDate.setDate(startDate.getDate() - lookbackDays);
|
|
12017
|
+
const endDate = /* @__PURE__ */ new Date();
|
|
12018
|
+
endDate.setDate(endDate.getDate() + lookforwardDays);
|
|
12019
|
+
const syncPromises = [];
|
|
12020
|
+
for (const docSnapshot of practitionersSnapshot.docs) {
|
|
12021
|
+
const practitionerId = docSnapshot.id;
|
|
12022
|
+
syncPromises.push(
|
|
12023
|
+
this.importEventsFromExternalCalendars(
|
|
12024
|
+
"doctor",
|
|
12025
|
+
practitionerId,
|
|
12026
|
+
startDate,
|
|
12027
|
+
endDate
|
|
12028
|
+
).then((count) => {
|
|
12029
|
+
console.log(
|
|
12030
|
+
`Imported ${count} events for doctor ${practitionerId}`
|
|
12031
|
+
);
|
|
12032
|
+
}).catch((error) => {
|
|
12033
|
+
console.error(
|
|
12034
|
+
`Error importing events for doctor ${practitionerId}:`,
|
|
12035
|
+
error
|
|
12036
|
+
);
|
|
12037
|
+
})
|
|
12038
|
+
);
|
|
12039
|
+
syncPromises.push(
|
|
12040
|
+
this.updateExistingEventsFromExternalCalendars(
|
|
12041
|
+
practitionerId,
|
|
12042
|
+
startDate,
|
|
12043
|
+
endDate
|
|
12044
|
+
).then((count) => {
|
|
12045
|
+
console.log(
|
|
12046
|
+
`Updated ${count} events for doctor ${practitionerId}`
|
|
12047
|
+
);
|
|
12048
|
+
}).catch((error) => {
|
|
12049
|
+
console.error(
|
|
12050
|
+
`Error updating events for doctor ${practitionerId}:`,
|
|
12051
|
+
error
|
|
12052
|
+
);
|
|
12053
|
+
})
|
|
12054
|
+
);
|
|
12055
|
+
}
|
|
12056
|
+
await Promise.all(syncPromises);
|
|
12057
|
+
console.log("Completed external calendar synchronization");
|
|
12058
|
+
} catch (error) {
|
|
12059
|
+
console.error("Error synchronizing external calendars:", error);
|
|
12060
|
+
}
|
|
12061
|
+
}
|
|
12062
|
+
/**
|
|
12063
|
+
* Updates existing events that were synced from external calendars
|
|
12064
|
+
* @param doctorId - ID of the doctor
|
|
12065
|
+
* @param startDate - Start date for fetching events
|
|
12066
|
+
* @param endDate - End date for fetching events
|
|
12067
|
+
* @returns Number of events updated
|
|
12068
|
+
*/
|
|
12069
|
+
async updateExistingEventsFromExternalCalendars(doctorId, startDate, endDate) {
|
|
12070
|
+
var _a;
|
|
12071
|
+
try {
|
|
12072
|
+
const eventsRef = collection23(
|
|
12073
|
+
this.db,
|
|
12074
|
+
PRACTITIONERS_COLLECTION,
|
|
12075
|
+
doctorId,
|
|
12076
|
+
CALENDAR_COLLECTION
|
|
12077
|
+
);
|
|
12078
|
+
const q = query23(
|
|
12079
|
+
eventsRef,
|
|
12080
|
+
where23("syncStatus", "==", "external" /* EXTERNAL */),
|
|
12081
|
+
where23("eventTime.start", ">=", Timestamp26.fromDate(startDate)),
|
|
12082
|
+
where23("eventTime.start", "<=", Timestamp26.fromDate(endDate))
|
|
12083
|
+
);
|
|
12084
|
+
const eventsSnapshot = await getDocs23(q);
|
|
12085
|
+
const events = eventsSnapshot.docs.map((doc32) => ({
|
|
12086
|
+
id: doc32.id,
|
|
12087
|
+
...doc32.data()
|
|
12088
|
+
}));
|
|
12089
|
+
const calendars = await this.syncedCalendarsService.getPractitionerSyncedCalendars(
|
|
12090
|
+
doctorId
|
|
12091
|
+
);
|
|
12092
|
+
const activeCalendars = calendars.filter((cal) => cal.isActive);
|
|
12093
|
+
if (activeCalendars.length === 0 || events.length === 0) {
|
|
12094
|
+
return 0;
|
|
12095
|
+
}
|
|
12096
|
+
let updatedCount = 0;
|
|
12097
|
+
for (const event of events) {
|
|
12098
|
+
if (!((_a = event.syncedCalendarEventId) == null ? void 0 : _a.length)) continue;
|
|
12099
|
+
for (const syncId of event.syncedCalendarEventId) {
|
|
12100
|
+
const calendar = activeCalendars.find(
|
|
12101
|
+
(cal) => cal.provider === syncId.syncedCalendarProvider
|
|
12102
|
+
);
|
|
12103
|
+
if (!calendar) continue;
|
|
12104
|
+
if (syncId.syncedCalendarProvider === "google" /* GOOGLE */) {
|
|
12105
|
+
try {
|
|
12106
|
+
const externalEvent = await this.fetchExternalEvent(
|
|
12107
|
+
doctorId,
|
|
12108
|
+
calendar,
|
|
12109
|
+
syncId.eventId
|
|
12110
|
+
);
|
|
12111
|
+
if (externalEvent) {
|
|
12112
|
+
const externalStartTime = new Date(
|
|
12113
|
+
externalEvent.start.dateTime || externalEvent.start.date
|
|
12114
|
+
).getTime();
|
|
12115
|
+
const externalEndTime = new Date(
|
|
12116
|
+
externalEvent.end.dateTime || externalEvent.end.date
|
|
12117
|
+
).getTime();
|
|
12118
|
+
const localStartTime = event.eventTime.start.toDate().getTime();
|
|
12119
|
+
const localEndTime = event.eventTime.end.toDate().getTime();
|
|
12120
|
+
if (externalStartTime !== localStartTime || externalEndTime !== localEndTime || externalEvent.summary !== event.eventName || externalEvent.description !== event.description) {
|
|
12121
|
+
await this.updateLocalEventFromExternal(
|
|
12122
|
+
doctorId,
|
|
12123
|
+
event.id,
|
|
12124
|
+
externalEvent
|
|
12125
|
+
);
|
|
12126
|
+
updatedCount++;
|
|
12127
|
+
}
|
|
12128
|
+
} else {
|
|
12129
|
+
await this.updateEventStatus(
|
|
12130
|
+
doctorId,
|
|
12131
|
+
event.id,
|
|
12132
|
+
"canceled" /* CANCELED */
|
|
12133
|
+
);
|
|
12134
|
+
updatedCount++;
|
|
12135
|
+
}
|
|
12136
|
+
} catch (error) {
|
|
12137
|
+
console.error(
|
|
12138
|
+
`Error updating external event ${event.id}:`,
|
|
12139
|
+
error
|
|
12140
|
+
);
|
|
12141
|
+
}
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
12144
|
+
}
|
|
12145
|
+
return updatedCount;
|
|
12146
|
+
} catch (error) {
|
|
12147
|
+
console.error(
|
|
12148
|
+
"Error updating existing events from external calendars:",
|
|
12149
|
+
error
|
|
12150
|
+
);
|
|
12151
|
+
return 0;
|
|
12152
|
+
}
|
|
12153
|
+
}
|
|
12154
|
+
/**
|
|
12155
|
+
* Fetches a single external event from Google Calendar
|
|
12156
|
+
* @param doctorId - ID of the doctor
|
|
12157
|
+
* @param calendar - Calendar information
|
|
12158
|
+
* @param externalEventId - ID of the external event
|
|
12159
|
+
* @returns External event data or null if not found
|
|
12160
|
+
*/
|
|
12161
|
+
async fetchExternalEvent(doctorId, calendar, externalEventId) {
|
|
12162
|
+
try {
|
|
12163
|
+
if (calendar.provider === "google" /* GOOGLE */) {
|
|
12164
|
+
const result = await this.syncedCalendarsService.fetchEventFromPractitionerGoogleCalendar(
|
|
12165
|
+
doctorId,
|
|
12166
|
+
calendar.id,
|
|
12167
|
+
externalEventId
|
|
12168
|
+
);
|
|
12169
|
+
return result;
|
|
12170
|
+
}
|
|
12171
|
+
return null;
|
|
12172
|
+
} catch (error) {
|
|
12173
|
+
console.error(`Error fetching external event ${externalEventId}:`, error);
|
|
12174
|
+
return null;
|
|
12175
|
+
}
|
|
12176
|
+
}
|
|
12177
|
+
/**
|
|
12178
|
+
* Updates a local event with data from an external event
|
|
12179
|
+
* @param doctorId - ID of the doctor
|
|
12180
|
+
* @param eventId - ID of the local event
|
|
12181
|
+
* @param externalEvent - External event data
|
|
12182
|
+
*/
|
|
12183
|
+
async updateLocalEventFromExternal(doctorId, eventId, externalEvent) {
|
|
12184
|
+
try {
|
|
12185
|
+
const startTime = new Date(
|
|
12186
|
+
externalEvent.start.dateTime || externalEvent.start.date
|
|
12187
|
+
);
|
|
12188
|
+
const endTime = new Date(
|
|
12189
|
+
externalEvent.end.dateTime || externalEvent.end.date
|
|
12190
|
+
);
|
|
12191
|
+
const eventRef = doc23(
|
|
12192
|
+
this.db,
|
|
12193
|
+
PRACTITIONERS_COLLECTION,
|
|
12194
|
+
doctorId,
|
|
12195
|
+
CALENDAR_COLLECTION,
|
|
12196
|
+
eventId
|
|
12197
|
+
);
|
|
12198
|
+
await updateDoc22(eventRef, {
|
|
12199
|
+
eventName: externalEvent.summary || "External Event",
|
|
12200
|
+
eventTime: {
|
|
12201
|
+
start: Timestamp26.fromDate(startTime),
|
|
12202
|
+
end: Timestamp26.fromDate(endTime)
|
|
12203
|
+
},
|
|
12204
|
+
description: externalEvent.description || "",
|
|
12205
|
+
updatedAt: serverTimestamp20()
|
|
12206
|
+
});
|
|
12207
|
+
console.log(`Updated local event ${eventId} from external event`);
|
|
12208
|
+
} catch (error) {
|
|
12209
|
+
console.error(
|
|
12210
|
+
`Error updating local event ${eventId} from external:`,
|
|
12211
|
+
error
|
|
12212
|
+
);
|
|
12213
|
+
}
|
|
12214
|
+
}
|
|
12215
|
+
/**
|
|
12216
|
+
* Updates an event's status
|
|
12217
|
+
* @param doctorId - ID of the doctor
|
|
12218
|
+
* @param eventId - ID of the event
|
|
12219
|
+
* @param status - New status
|
|
12220
|
+
*/
|
|
12221
|
+
async updateEventStatus(doctorId, eventId, status) {
|
|
12222
|
+
try {
|
|
12223
|
+
const eventRef = doc23(
|
|
12224
|
+
this.db,
|
|
12225
|
+
PRACTITIONERS_COLLECTION,
|
|
12226
|
+
doctorId,
|
|
12227
|
+
CALENDAR_COLLECTION,
|
|
12228
|
+
eventId
|
|
12229
|
+
);
|
|
12230
|
+
await updateDoc22(eventRef, {
|
|
12231
|
+
status,
|
|
12232
|
+
updatedAt: serverTimestamp20()
|
|
12233
|
+
});
|
|
12234
|
+
console.log(`Updated event ${eventId} status to ${status}`);
|
|
12235
|
+
} catch (error) {
|
|
12236
|
+
console.error(`Error updating event ${eventId} status:`, error);
|
|
12237
|
+
}
|
|
12238
|
+
}
|
|
12239
|
+
/**
|
|
12240
|
+
* Creates a scheduled job to periodically sync external calendars
|
|
12241
|
+
* Note: This would be implemented using Cloud Functions in a real application
|
|
12242
|
+
* This is a sample implementation to show how it could be set up
|
|
12243
|
+
* @param interval - Interval in hours
|
|
12244
|
+
*/
|
|
12245
|
+
createScheduledSyncJob(interval = 3) {
|
|
12246
|
+
console.log(
|
|
12247
|
+
`Setting up scheduled calendar sync job every ${interval} hours`
|
|
12248
|
+
);
|
|
12249
|
+
}
|
|
12250
|
+
/**
|
|
12251
|
+
* Searches for calendar events based on specified criteria.
|
|
12252
|
+
*
|
|
12253
|
+
* @param {SearchCalendarEventsParams} params - The search parameters.
|
|
12254
|
+
* @param {SearchLocationEnum} params.searchLocation - The primary location to search (practitioner, patient, or clinic).
|
|
12255
|
+
* @param {string} params.entityId - The ID of the entity (practitioner, patient, or clinic) to search within/for.
|
|
12256
|
+
* @param {string} [params.clinicId] - Optional clinic ID to filter by.
|
|
12257
|
+
* @param {string} [params.practitionerId] - Optional practitioner ID to filter by.
|
|
12258
|
+
* @param {string} [params.patientId] - Optional patient ID to filter by.
|
|
12259
|
+
* @param {string} [params.procedureId] - Optional procedure ID to filter by.
|
|
12260
|
+
* @param {DateRange} [params.dateRange] - Optional date range to filter by (event start time).
|
|
12261
|
+
* @param {CalendarEventStatus} [params.eventStatus] - Optional event status to filter by.
|
|
12262
|
+
* @param {CalendarEventType} [params.eventType] - Optional event type to filter by.
|
|
12263
|
+
* @returns {Promise<CalendarEvent[]>} A promise that resolves to an array of matching calendar events.
|
|
12264
|
+
* @throws {Error} If the search location requires an entity ID that is not provided.
|
|
12265
|
+
*/
|
|
12266
|
+
async searchCalendarEvents(params) {
|
|
12267
|
+
return searchCalendarEventsUtil(this.db, params);
|
|
12268
|
+
}
|
|
12269
|
+
/**
|
|
12270
|
+
* Gets a doctor's upcoming appointments for a specific date range
|
|
12271
|
+
*
|
|
12272
|
+
* @param {string} doctorId - ID of the practitioner
|
|
12273
|
+
* @param {Date} startDate - Start date of the range
|
|
12274
|
+
* @param {Date} endDate - End date of the range
|
|
12275
|
+
* @param {CalendarEventStatus} [status] - Optional status filter (defaults to CONFIRMED)
|
|
12276
|
+
* @returns {Promise<CalendarEvent[]>} A promise that resolves to an array of appointments
|
|
12277
|
+
*/
|
|
12278
|
+
async getPractitionerUpcomingAppointments(doctorId, startDate, endDate, status = "confirmed" /* CONFIRMED */) {
|
|
12279
|
+
const dateRange = {
|
|
12280
|
+
start: Timestamp26.fromDate(startDate),
|
|
12281
|
+
end: Timestamp26.fromDate(endDate)
|
|
12282
|
+
};
|
|
12283
|
+
const searchParams = {
|
|
12284
|
+
searchLocation: "practitioner" /* PRACTITIONER */,
|
|
12285
|
+
entityId: doctorId,
|
|
12286
|
+
dateRange,
|
|
12287
|
+
eventStatus: status,
|
|
12288
|
+
eventType: "appointment" /* APPOINTMENT */
|
|
12289
|
+
};
|
|
12290
|
+
return this.searchCalendarEvents(searchParams);
|
|
12291
|
+
}
|
|
12292
|
+
/**
|
|
12293
|
+
* Gets a patient's appointments for a specific date range
|
|
12294
|
+
*
|
|
12295
|
+
* @param {string} patientId - ID of the patient
|
|
12296
|
+
* @param {Date} startDate - Start date of the range
|
|
12297
|
+
* @param {Date} endDate - End date of the range
|
|
12298
|
+
* @param {CalendarEventStatus} [status] - Optional status filter (defaults to all non-canceled appointments)
|
|
12299
|
+
* @returns {Promise<CalendarEvent[]>} A promise that resolves to an array of appointments
|
|
12300
|
+
*/
|
|
12301
|
+
async getPatientAppointments(patientId, startDate, endDate, status) {
|
|
12302
|
+
const dateRange = {
|
|
12303
|
+
start: Timestamp26.fromDate(startDate),
|
|
12304
|
+
end: Timestamp26.fromDate(endDate)
|
|
12305
|
+
};
|
|
12306
|
+
const searchParams = {
|
|
12307
|
+
searchLocation: "patient" /* PATIENT */,
|
|
12308
|
+
entityId: patientId,
|
|
12309
|
+
dateRange,
|
|
12310
|
+
eventType: "appointment" /* APPOINTMENT */
|
|
12311
|
+
};
|
|
12312
|
+
if (status) {
|
|
12313
|
+
searchParams.eventStatus = status;
|
|
12314
|
+
}
|
|
12315
|
+
return this.searchCalendarEvents(searchParams);
|
|
12316
|
+
}
|
|
12317
|
+
/**
|
|
12318
|
+
* Gets all appointments for a clinic within a specific date range
|
|
12319
|
+
*
|
|
12320
|
+
* @param {string} clinicId - ID of the clinic
|
|
12321
|
+
* @param {Date} startDate - Start date of the range
|
|
12322
|
+
* @param {Date} endDate - End date of the range
|
|
12323
|
+
* @param {string} [doctorId] - Optional doctor ID to filter by
|
|
12324
|
+
* @param {CalendarEventStatus} [status] - Optional status filter
|
|
12325
|
+
* @returns {Promise<CalendarEvent[]>} A promise that resolves to an array of appointments
|
|
12326
|
+
*/
|
|
12327
|
+
async getClinicAppointments(clinicId, startDate, endDate, doctorId, status) {
|
|
12328
|
+
const dateRange = {
|
|
12329
|
+
start: Timestamp26.fromDate(startDate),
|
|
12330
|
+
end: Timestamp26.fromDate(endDate)
|
|
12331
|
+
};
|
|
12332
|
+
const searchParams = {
|
|
12333
|
+
searchLocation: "clinic" /* CLINIC */,
|
|
12334
|
+
entityId: clinicId,
|
|
12335
|
+
dateRange,
|
|
12336
|
+
eventType: "appointment" /* APPOINTMENT */
|
|
12337
|
+
};
|
|
12338
|
+
if (doctorId) {
|
|
12339
|
+
searchParams.practitionerId = doctorId;
|
|
12340
|
+
}
|
|
12341
|
+
if (status) {
|
|
12342
|
+
searchParams.eventStatus = status;
|
|
12343
|
+
}
|
|
12344
|
+
return this.searchCalendarEvents(searchParams);
|
|
12345
|
+
}
|
|
12346
|
+
// #endregion
|
|
12347
|
+
// #region Private Helper Methods
|
|
12348
|
+
/**
|
|
12349
|
+
* Validates appointment creation parameters
|
|
12350
|
+
* @param params - Appointment parameters to validate
|
|
12351
|
+
* @throws Error if validation fails
|
|
12352
|
+
*/
|
|
12353
|
+
async validateAppointmentParams(params) {
|
|
12354
|
+
await createAppointmentSchema.parseAsync(params);
|
|
12355
|
+
}
|
|
12356
|
+
/**
|
|
12357
|
+
* Validates if the event time falls within clinic working hours
|
|
12358
|
+
* @param clinicId - ID of the clinic
|
|
12359
|
+
* @param eventTime - Event time to validate
|
|
12360
|
+
* @throws Error if validation fails
|
|
12361
|
+
*/
|
|
12362
|
+
async validateClinicWorkingHours(clinicId, eventTime) {
|
|
12363
|
+
const startDate = eventTime.start.toDate();
|
|
12364
|
+
const workingHours = await this.getClinicWorkingHours(clinicId, startDate);
|
|
12365
|
+
if (workingHours.length === 0) {
|
|
12366
|
+
throw new Error("Clinic is not open on this day");
|
|
12367
|
+
}
|
|
12368
|
+
const startTime = startDate;
|
|
12369
|
+
const endTime = eventTime.end.toDate();
|
|
12370
|
+
const isWithinWorkingHours = workingHours.some((slot) => {
|
|
12371
|
+
return slot.start <= startTime && slot.end >= endTime && slot.isAvailable;
|
|
12372
|
+
});
|
|
12373
|
+
if (!isWithinWorkingHours) {
|
|
12374
|
+
throw new Error("Appointment time is outside clinic working hours");
|
|
12375
|
+
}
|
|
12376
|
+
}
|
|
12377
|
+
/**
|
|
12378
|
+
* Validates if the doctor is available during the event time
|
|
12379
|
+
* @param doctorId - ID of the doctor
|
|
12380
|
+
* @param eventTime - Event time to validate
|
|
12381
|
+
* @param clinicId - ID of the clinic where the appointment is being booked
|
|
12382
|
+
* @throws Error if validation fails
|
|
12383
|
+
*/
|
|
12384
|
+
async validateDoctorAvailability(doctorId, eventTime, clinicId) {
|
|
12385
|
+
var _a;
|
|
12386
|
+
const startDate = eventTime.start.toDate();
|
|
12387
|
+
const startTime = startDate;
|
|
12388
|
+
const endTime = eventTime.end.toDate();
|
|
12389
|
+
const practitionerRef = doc23(this.db, PRACTITIONERS_COLLECTION, doctorId);
|
|
12390
|
+
const practitionerDoc = await getDoc25(practitionerRef);
|
|
12391
|
+
if (!practitionerDoc.exists()) {
|
|
12392
|
+
throw new Error(`Doctor with ID ${doctorId} not found`);
|
|
12393
|
+
}
|
|
12394
|
+
const practitioner = practitionerDoc.data();
|
|
12395
|
+
if (!practitioner.clinics.includes(clinicId)) {
|
|
12396
|
+
throw new Error("Doctor does not work at this clinic");
|
|
12397
|
+
}
|
|
12398
|
+
const clinicWorkingHours = (_a = practitioner.clinicWorkingHours) == null ? void 0 : _a.find(
|
|
12399
|
+
(hours) => hours.clinicId === clinicId && hours.isActive
|
|
12400
|
+
);
|
|
12401
|
+
if (!clinicWorkingHours) {
|
|
12402
|
+
throw new Error("Doctor does not have working hours set for this clinic");
|
|
12403
|
+
}
|
|
12404
|
+
const dayOfWeek = startDate.getDay();
|
|
12405
|
+
const dayKey = [
|
|
12406
|
+
"sunday",
|
|
12407
|
+
"monday",
|
|
12408
|
+
"tuesday",
|
|
12409
|
+
"wednesday",
|
|
12410
|
+
"thursday",
|
|
12411
|
+
"friday",
|
|
12412
|
+
"saturday"
|
|
12413
|
+
][dayOfWeek];
|
|
12414
|
+
const daySchedule = clinicWorkingHours.workingHours[dayKey];
|
|
12415
|
+
if (!daySchedule) {
|
|
12416
|
+
throw new Error("Doctor is not working on this day at this clinic");
|
|
12417
|
+
}
|
|
12418
|
+
const [startHour, startMinute] = daySchedule.start.split(":").map(Number);
|
|
12419
|
+
const [endHour, endMinute] = daySchedule.end.split(":").map(Number);
|
|
12420
|
+
const scheduleStart = new Date(startDate);
|
|
12421
|
+
scheduleStart.setHours(startHour, startMinute, 0, 0);
|
|
12422
|
+
const scheduleEnd = new Date(startDate);
|
|
12423
|
+
scheduleEnd.setHours(endHour, endMinute, 0, 0);
|
|
12424
|
+
if (startTime < scheduleStart || endTime > scheduleEnd) {
|
|
12425
|
+
throw new Error(
|
|
12426
|
+
"Appointment time is outside doctor's working hours at this clinic"
|
|
12427
|
+
);
|
|
12428
|
+
}
|
|
12429
|
+
const appointments = await this.getDoctorAppointments(doctorId, startDate);
|
|
12430
|
+
const hasOverlap = appointments.some((appointment) => {
|
|
12431
|
+
const appointmentStart = appointment.eventTime.start.toDate();
|
|
12432
|
+
const appointmentEnd = appointment.eventTime.end.toDate();
|
|
12433
|
+
return startTime >= appointmentStart && startTime < appointmentEnd || endTime > appointmentStart && endTime <= appointmentEnd || startTime <= appointmentStart && endTime >= appointmentEnd;
|
|
12434
|
+
});
|
|
12435
|
+
if (hasOverlap) {
|
|
12436
|
+
throw new Error("Doctor has another appointment during this time");
|
|
12437
|
+
}
|
|
12438
|
+
}
|
|
12439
|
+
/**
|
|
12440
|
+
* Updates appointment status
|
|
12441
|
+
* @param appointmentId - ID of the appointment
|
|
12442
|
+
* @param clinicId - ID of the clinic
|
|
12443
|
+
* @param status - New status
|
|
12444
|
+
* @returns Updated calendar event
|
|
12445
|
+
*/
|
|
12446
|
+
async updateAppointmentStatus(appointmentId, clinicId, status) {
|
|
12447
|
+
const baseCollectionPath = `${CLINICS_COLLECTION}/${clinicId}/${CALENDAR_COLLECTION}`;
|
|
12448
|
+
const appointmentRef = doc23(this.db, baseCollectionPath, appointmentId);
|
|
12449
|
+
const appointmentDoc = await getDoc25(appointmentRef);
|
|
12450
|
+
if (!appointmentDoc.exists()) {
|
|
12451
|
+
throw new Error(`Appointment with ID ${appointmentId} not found`);
|
|
12452
|
+
}
|
|
12453
|
+
const appointment = appointmentDoc.data();
|
|
12454
|
+
if (appointment.clinicBranchId !== clinicId) {
|
|
12455
|
+
throw new Error("Appointment does not belong to the specified clinic");
|
|
12456
|
+
}
|
|
12457
|
+
this.validateStatusTransition(appointment.status, status);
|
|
12458
|
+
const updateParams = {
|
|
12459
|
+
appointmentId,
|
|
12460
|
+
clinicId,
|
|
12461
|
+
eventTime: appointment.eventTime,
|
|
12462
|
+
description: appointment.description || "",
|
|
12463
|
+
doctorId: appointment.practitionerProfileId || "",
|
|
12464
|
+
patientId: appointment.patientProfileId || "",
|
|
12465
|
+
status
|
|
12466
|
+
};
|
|
12467
|
+
await this.validateUpdatePermissions(updateParams);
|
|
12468
|
+
return this.updateAppointment(updateParams);
|
|
12469
|
+
}
|
|
12470
|
+
/**
|
|
12471
|
+
* Validates status transition
|
|
12472
|
+
* @param currentStatus - Current status
|
|
12473
|
+
* @param newStatus - New status
|
|
12474
|
+
* @throws Error if transition is invalid
|
|
12475
|
+
*/
|
|
12476
|
+
validateStatusTransition(currentStatus, newStatus) {
|
|
12477
|
+
const validTransitions = {
|
|
12478
|
+
["pending" /* PENDING */]: [
|
|
12479
|
+
"confirmed" /* CONFIRMED */,
|
|
12480
|
+
"rejected" /* REJECTED */,
|
|
12481
|
+
"canceled" /* CANCELED */
|
|
12482
|
+
],
|
|
12483
|
+
["confirmed" /* CONFIRMED */]: [
|
|
12484
|
+
"canceled" /* CANCELED */,
|
|
12485
|
+
"completed" /* COMPLETED */,
|
|
12486
|
+
"rescheduled" /* RESCHEDULED */,
|
|
12487
|
+
"no_show" /* NO_SHOW */
|
|
12488
|
+
],
|
|
12489
|
+
["rejected" /* REJECTED */]: [],
|
|
12490
|
+
["canceled" /* CANCELED */]: [],
|
|
12491
|
+
["rescheduled" /* RESCHEDULED */]: [
|
|
12492
|
+
"confirmed" /* CONFIRMED */,
|
|
12493
|
+
"canceled" /* CANCELED */
|
|
12494
|
+
],
|
|
12495
|
+
["completed" /* COMPLETED */]: [],
|
|
12496
|
+
["no_show" /* NO_SHOW */]: []
|
|
12497
|
+
};
|
|
12498
|
+
if (!validTransitions[currentStatus].includes(newStatus)) {
|
|
12499
|
+
throw new Error(
|
|
12500
|
+
`Invalid status transition from ${currentStatus} to ${newStatus}`
|
|
12501
|
+
);
|
|
12502
|
+
}
|
|
12503
|
+
}
|
|
12504
|
+
/**
|
|
12505
|
+
* Syncs appointment with external calendars based on entity type and status
|
|
12506
|
+
* @param appointment - Calendar event to sync
|
|
12507
|
+
*/
|
|
12508
|
+
async syncAppointmentWithExternalCalendars(appointment) {
|
|
12509
|
+
if (!appointment.practitionerProfileId || !appointment.patientProfileId) {
|
|
12510
|
+
return;
|
|
12511
|
+
}
|
|
12512
|
+
try {
|
|
12513
|
+
const [doctorCalendars, patientCalendars] = await Promise.all([
|
|
12514
|
+
this.syncedCalendarsService.getPractitionerSyncedCalendars(
|
|
12515
|
+
appointment.practitionerProfileId
|
|
12516
|
+
),
|
|
12517
|
+
this.syncedCalendarsService.getPatientSyncedCalendars(
|
|
12518
|
+
appointment.patientProfileId
|
|
12519
|
+
)
|
|
12520
|
+
]);
|
|
12521
|
+
const activeDoctorCalendars = doctorCalendars.filter(
|
|
12522
|
+
(cal) => cal.isActive
|
|
12523
|
+
);
|
|
12524
|
+
const activePatientCalendars = patientCalendars.filter(
|
|
12525
|
+
(cal) => cal.isActive
|
|
12526
|
+
);
|
|
12527
|
+
if (activeDoctorCalendars.length === 0 && activePatientCalendars.length === 0) {
|
|
12528
|
+
return;
|
|
12529
|
+
}
|
|
12530
|
+
if (appointment.syncStatus !== "internal" /* INTERNAL */) {
|
|
12531
|
+
return;
|
|
10277
12532
|
}
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
"Patient ID (entityId) is required when searching patient calendar."
|
|
12533
|
+
if (appointment.status === "confirmed" /* CONFIRMED */ && activeDoctorCalendars.length > 0) {
|
|
12534
|
+
await Promise.all(
|
|
12535
|
+
activeDoctorCalendars.map(
|
|
12536
|
+
(calendar) => this.syncEventToExternalCalendar(appointment, calendar, "doctor")
|
|
12537
|
+
)
|
|
10284
12538
|
);
|
|
10285
12539
|
}
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
12540
|
+
if (appointment.status !== "canceled" /* CANCELED */ && appointment.status !== "rejected" /* REJECTED */ && activePatientCalendars.length > 0) {
|
|
12541
|
+
await Promise.all(
|
|
12542
|
+
activePatientCalendars.map(
|
|
12543
|
+
(calendar) => this.syncEventToExternalCalendar(appointment, calendar, "patient")
|
|
12544
|
+
)
|
|
10290
12545
|
);
|
|
10291
|
-
return [];
|
|
10292
12546
|
}
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
12547
|
+
} catch (error) {
|
|
12548
|
+
console.error("Error syncing with external calendars:", error);
|
|
12549
|
+
}
|
|
12550
|
+
}
|
|
12551
|
+
/**
|
|
12552
|
+
* Syncs a single event to an external calendar
|
|
12553
|
+
* @param appointment - Calendar event to sync
|
|
12554
|
+
* @param calendar - External calendar to sync with
|
|
12555
|
+
* @param entityType - Type of entity owning the calendar
|
|
12556
|
+
*/
|
|
12557
|
+
async syncEventToExternalCalendar(appointment, calendar, entityType) {
|
|
12558
|
+
var _a, _b, _c, _d, _e;
|
|
12559
|
+
try {
|
|
12560
|
+
const eventToSync = { ...appointment };
|
|
12561
|
+
let eventTitle = appointment.eventName;
|
|
12562
|
+
const clinicName = ((_a = appointment.clinicBranchInfo) == null ? void 0 : _a.name) || "Clinic";
|
|
12563
|
+
if (entityType === "patient") {
|
|
12564
|
+
eventTitle = `[${appointment.status}] ${eventTitle} @ ${clinicName}`;
|
|
12565
|
+
} else {
|
|
12566
|
+
eventTitle = `${eventTitle} - Patient: ${((_b = appointment.patientProfileInfo) == null ? void 0 : _b.fullName) || "Unknown"} @ ${clinicName}`;
|
|
10300
12567
|
}
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
12568
|
+
eventToSync.eventName = eventTitle;
|
|
12569
|
+
const existingSyncId = (_d = (_c = appointment.syncedCalendarEventId) == null ? void 0 : _c.find(
|
|
12570
|
+
(sync) => sync.syncedCalendarProvider === calendar.provider
|
|
12571
|
+
)) == null ? void 0 : _d.eventId;
|
|
12572
|
+
if (calendar.provider === "google" /* GOOGLE */) {
|
|
12573
|
+
const result = await this.syncedCalendarsService.syncPractitionerEventsToGoogleCalendar(
|
|
12574
|
+
entityType === "doctor" ? appointment.practitionerProfileId : appointment.patientProfileId,
|
|
12575
|
+
calendar.id,
|
|
12576
|
+
[eventToSync],
|
|
12577
|
+
existingSyncId
|
|
12578
|
+
// Pass existing sync ID if we have one
|
|
10306
12579
|
);
|
|
10307
|
-
|
|
12580
|
+
if (result.success && ((_e = result.eventIds) == null ? void 0 : _e.length) && !existingSyncId) {
|
|
12581
|
+
const newSyncEvent = {
|
|
12582
|
+
eventId: result.eventIds[0],
|
|
12583
|
+
syncedCalendarProvider: calendar.provider,
|
|
12584
|
+
syncedAt: Timestamp26.now()
|
|
12585
|
+
};
|
|
12586
|
+
await this.updateEventWithSyncId(
|
|
12587
|
+
entityType === "doctor" ? appointment.practitionerProfileId : appointment.patientProfileId,
|
|
12588
|
+
entityType,
|
|
12589
|
+
appointment.id,
|
|
12590
|
+
newSyncEvent
|
|
12591
|
+
);
|
|
12592
|
+
}
|
|
10308
12593
|
}
|
|
10309
|
-
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
throw new Error(`Invalid search location: ${searchLocation}`);
|
|
10313
|
-
}
|
|
10314
|
-
const collectionRef = collection18(db, baseCollectionPath);
|
|
10315
|
-
if (filters.clinicId) {
|
|
10316
|
-
constraints.push(where18("clinicBranchId", "==", filters.clinicId));
|
|
12594
|
+
} catch (error) {
|
|
12595
|
+
console.error(`Error syncing with ${entityType}'s calendar:`, error);
|
|
12596
|
+
}
|
|
10317
12597
|
}
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
12598
|
+
/**
|
|
12599
|
+
* Updates an event with a new sync ID
|
|
12600
|
+
* @param entityId - ID of the entity (doctor or patient)
|
|
12601
|
+
* @param entityType - Type of entity
|
|
12602
|
+
* @param eventId - ID of the event
|
|
12603
|
+
* @param syncEvent - Sync event information
|
|
12604
|
+
*/
|
|
12605
|
+
async updateEventWithSyncId(entityId, entityType, eventId, syncEvent) {
|
|
12606
|
+
try {
|
|
12607
|
+
const collectionPath = entityType === "doctor" ? `${PRACTITIONERS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}` : `${PATIENTS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
|
|
12608
|
+
const eventRef = doc23(this.db, collectionPath, eventId);
|
|
12609
|
+
const eventDoc = await getDoc25(eventRef);
|
|
12610
|
+
if (eventDoc.exists()) {
|
|
12611
|
+
const event = eventDoc.data();
|
|
12612
|
+
const syncIds = [...event.syncedCalendarEventId || []];
|
|
12613
|
+
const existingSyncIndex = syncIds.findIndex(
|
|
12614
|
+
(sync) => sync.syncedCalendarProvider === syncEvent.syncedCalendarProvider
|
|
12615
|
+
);
|
|
12616
|
+
if (existingSyncIndex >= 0) {
|
|
12617
|
+
syncIds[existingSyncIndex] = syncEvent;
|
|
12618
|
+
} else {
|
|
12619
|
+
syncIds.push(syncEvent);
|
|
12620
|
+
}
|
|
12621
|
+
await updateDoc22(eventRef, {
|
|
12622
|
+
syncedCalendarEventId: syncIds,
|
|
12623
|
+
updatedAt: serverTimestamp20()
|
|
12624
|
+
});
|
|
12625
|
+
console.log(
|
|
12626
|
+
`Updated event ${eventId} with sync ID ${syncEvent.eventId}`
|
|
12627
|
+
);
|
|
12628
|
+
}
|
|
12629
|
+
} catch (error) {
|
|
12630
|
+
console.error("Error updating event with sync ID:", error);
|
|
12631
|
+
}
|
|
10322
12632
|
}
|
|
10323
|
-
|
|
10324
|
-
|
|
12633
|
+
/**
|
|
12634
|
+
* Validates update permissions and parameters
|
|
12635
|
+
* @param params - Update parameters to validate
|
|
12636
|
+
*/
|
|
12637
|
+
async validateUpdatePermissions(params) {
|
|
12638
|
+
await updateAppointmentSchema.parseAsync(params);
|
|
10325
12639
|
}
|
|
10326
|
-
|
|
10327
|
-
|
|
12640
|
+
/**
|
|
12641
|
+
* Gets clinic working hours for a specific date
|
|
12642
|
+
* @param clinicId - ID of the clinic
|
|
12643
|
+
* @param date - Date to get working hours for
|
|
12644
|
+
* @returns Working hours for the clinic
|
|
12645
|
+
*/
|
|
12646
|
+
async getClinicWorkingHours(clinicId, date) {
|
|
12647
|
+
const clinicRef = doc23(this.db, CLINICS_COLLECTION, clinicId);
|
|
12648
|
+
const clinicDoc = await getDoc25(clinicRef);
|
|
12649
|
+
if (!clinicDoc.exists()) {
|
|
12650
|
+
throw new Error(`Clinic with ID ${clinicId} not found`);
|
|
12651
|
+
}
|
|
12652
|
+
const workingHours = [];
|
|
12653
|
+
const dayOfWeek = date.getDay();
|
|
12654
|
+
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
|
12655
|
+
return workingHours;
|
|
12656
|
+
}
|
|
12657
|
+
const workingDate = new Date(date);
|
|
12658
|
+
workingDate.setHours(9, 0, 0, 0);
|
|
12659
|
+
const startTime = new Date(workingDate);
|
|
12660
|
+
workingDate.setHours(17, 0, 0, 0);
|
|
12661
|
+
const endTime = new Date(workingDate);
|
|
12662
|
+
workingHours.push({
|
|
12663
|
+
start: startTime,
|
|
12664
|
+
end: endTime,
|
|
12665
|
+
isAvailable: true
|
|
12666
|
+
});
|
|
12667
|
+
return workingHours;
|
|
10328
12668
|
}
|
|
10329
|
-
|
|
10330
|
-
|
|
12669
|
+
/**
|
|
12670
|
+
* Gets doctor's schedule for a specific date
|
|
12671
|
+
* @param doctorId - ID of the doctor
|
|
12672
|
+
* @param date - Date to get schedule for
|
|
12673
|
+
* @returns Doctor's schedule
|
|
12674
|
+
*/
|
|
12675
|
+
async getDoctorSchedule(doctorId, date) {
|
|
12676
|
+
const practitionerRef = doc23(this.db, PRACTITIONERS_COLLECTION, doctorId);
|
|
12677
|
+
const practitionerDoc = await getDoc25(practitionerRef);
|
|
12678
|
+
if (!practitionerDoc.exists()) {
|
|
12679
|
+
throw new Error(`Doctor with ID ${doctorId} not found`);
|
|
12680
|
+
}
|
|
12681
|
+
const schedule = [];
|
|
12682
|
+
const dayOfWeek = date.getDay();
|
|
12683
|
+
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
|
12684
|
+
return schedule;
|
|
12685
|
+
}
|
|
12686
|
+
const scheduleDate = new Date(date);
|
|
12687
|
+
scheduleDate.setHours(9, 0, 0, 0);
|
|
12688
|
+
const startTime = new Date(scheduleDate);
|
|
12689
|
+
scheduleDate.setHours(17, 0, 0, 0);
|
|
12690
|
+
const endTime = new Date(scheduleDate);
|
|
12691
|
+
schedule.push({
|
|
12692
|
+
start: startTime,
|
|
12693
|
+
end: endTime,
|
|
12694
|
+
isAvailable: true
|
|
12695
|
+
});
|
|
12696
|
+
return schedule;
|
|
10331
12697
|
}
|
|
10332
|
-
|
|
10333
|
-
|
|
12698
|
+
/**
|
|
12699
|
+
* Gets doctor's appointments for a specific date
|
|
12700
|
+
* @param doctorId - ID of the doctor
|
|
12701
|
+
* @param date - Date to get appointments for
|
|
12702
|
+
* @returns Array of calendar events
|
|
12703
|
+
*/
|
|
12704
|
+
async getDoctorAppointments(doctorId, date) {
|
|
12705
|
+
const startOfDay = new Date(date);
|
|
12706
|
+
startOfDay.setHours(0, 0, 0, 0);
|
|
12707
|
+
const endOfDay = new Date(date);
|
|
12708
|
+
endOfDay.setHours(23, 59, 59, 999);
|
|
12709
|
+
const appointmentsRef = collection23(this.db, CALENDAR_COLLECTION);
|
|
12710
|
+
const q = query23(
|
|
12711
|
+
appointmentsRef,
|
|
12712
|
+
where23("practitionerProfileId", "==", doctorId),
|
|
12713
|
+
where23("eventTime.start", ">=", Timestamp26.fromDate(startOfDay)),
|
|
12714
|
+
where23("eventTime.start", "<=", Timestamp26.fromDate(endOfDay)),
|
|
12715
|
+
where23("status", "in", [
|
|
12716
|
+
"confirmed" /* CONFIRMED */,
|
|
12717
|
+
"pending" /* PENDING */
|
|
12718
|
+
])
|
|
12719
|
+
);
|
|
12720
|
+
const querySnapshot = await getDocs23(q);
|
|
12721
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
10334
12722
|
}
|
|
10335
|
-
|
|
10336
|
-
|
|
10337
|
-
|
|
12723
|
+
/**
|
|
12724
|
+
* Calculates available time slots based on working hours, schedule and existing appointments
|
|
12725
|
+
* @param workingHours - Clinic working hours
|
|
12726
|
+
* @param doctorSchedule - Doctor's schedule
|
|
12727
|
+
* @param existingAppointments - Existing appointments
|
|
12728
|
+
* @returns Array of available time slots
|
|
12729
|
+
*/
|
|
12730
|
+
calculateAvailableSlots(workingHours, doctorSchedule, existingAppointments) {
|
|
12731
|
+
const availableSlots = [];
|
|
12732
|
+
for (const workingHour of workingHours) {
|
|
12733
|
+
for (const scheduleSlot of doctorSchedule) {
|
|
12734
|
+
const overlapStart = new Date(
|
|
12735
|
+
Math.max(workingHour.start.getTime(), scheduleSlot.start.getTime())
|
|
12736
|
+
);
|
|
12737
|
+
const overlapEnd = new Date(
|
|
12738
|
+
Math.min(workingHour.end.getTime(), scheduleSlot.end.getTime())
|
|
12739
|
+
);
|
|
12740
|
+
if (overlapStart < overlapEnd && workingHour.isAvailable && scheduleSlot.isAvailable) {
|
|
12741
|
+
let slotStart = new Date(overlapStart);
|
|
12742
|
+
while (slotStart < overlapEnd) {
|
|
12743
|
+
const slotEnd = new Date(
|
|
12744
|
+
slotStart.getTime() + MIN_APPOINTMENT_DURATION * 60 * 1e3
|
|
12745
|
+
);
|
|
12746
|
+
const hasOverlap = existingAppointments.some((appointment) => {
|
|
12747
|
+
const appointmentStart = appointment.eventTime.start.toDate();
|
|
12748
|
+
const appointmentEnd = appointment.eventTime.end.toDate();
|
|
12749
|
+
return slotStart >= appointmentStart && slotStart < appointmentEnd || slotEnd > appointmentStart && slotEnd <= appointmentEnd;
|
|
12750
|
+
});
|
|
12751
|
+
if (!hasOverlap && slotEnd <= overlapEnd) {
|
|
12752
|
+
availableSlots.push({
|
|
12753
|
+
start: new Date(slotStart),
|
|
12754
|
+
end: new Date(slotEnd),
|
|
12755
|
+
isAvailable: true
|
|
12756
|
+
});
|
|
12757
|
+
}
|
|
12758
|
+
slotStart = new Date(
|
|
12759
|
+
slotStart.getTime() + MIN_APPOINTMENT_DURATION * 60 * 1e3
|
|
12760
|
+
);
|
|
12761
|
+
}
|
|
12762
|
+
}
|
|
12763
|
+
}
|
|
12764
|
+
}
|
|
12765
|
+
return availableSlots;
|
|
10338
12766
|
}
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
12767
|
+
/**
|
|
12768
|
+
* Fetches and creates info cards for clinic, doctor, and patient profiles
|
|
12769
|
+
* @param clinicId - ID of the clinic
|
|
12770
|
+
* @param doctorId - ID of the doctor
|
|
12771
|
+
* @param patientId - ID of the patient
|
|
12772
|
+
* @returns Object containing info cards for all profiles
|
|
12773
|
+
*/
|
|
12774
|
+
async fetchProfileInfoCards(clinicId, doctorId, patientId) {
|
|
12775
|
+
var _a;
|
|
12776
|
+
try {
|
|
12777
|
+
const [clinicDoc, practitionerDoc, patientDoc, patientSensitiveInfoDoc] = await Promise.all([
|
|
12778
|
+
getDoc25(doc23(this.db, CLINICS_COLLECTION, clinicId)),
|
|
12779
|
+
getDoc25(doc23(this.db, PRACTITIONERS_COLLECTION, doctorId)),
|
|
12780
|
+
getDoc25(doc23(this.db, PATIENTS_COLLECTION, patientId)),
|
|
12781
|
+
getDoc25(
|
|
12782
|
+
doc23(
|
|
12783
|
+
this.db,
|
|
12784
|
+
PATIENTS_COLLECTION,
|
|
12785
|
+
patientId,
|
|
12786
|
+
PATIENT_SENSITIVE_INFO_COLLECTION,
|
|
12787
|
+
patientId
|
|
12788
|
+
)
|
|
12789
|
+
)
|
|
12790
|
+
]);
|
|
12791
|
+
const clinicInfo = clinicDoc.exists() ? {
|
|
12792
|
+
id: clinicDoc.id,
|
|
12793
|
+
featuredPhoto: clinicDoc.data().featuredPhoto || "",
|
|
12794
|
+
name: clinicDoc.data().name,
|
|
12795
|
+
description: clinicDoc.data().description || "",
|
|
12796
|
+
location: clinicDoc.data().location,
|
|
12797
|
+
contactInfo: clinicDoc.data().contactInfo
|
|
12798
|
+
} : null;
|
|
12799
|
+
const practitionerInfo = practitionerDoc.exists() ? {
|
|
12800
|
+
id: practitionerDoc.id,
|
|
12801
|
+
practitionerPhoto: practitionerDoc.data().basicInfo.profileImageUrl || null,
|
|
12802
|
+
name: `${practitionerDoc.data().basicInfo.firstName} ${practitionerDoc.data().basicInfo.lastName}`,
|
|
12803
|
+
email: practitionerDoc.data().basicInfo.email,
|
|
12804
|
+
phone: practitionerDoc.data().basicInfo.phoneNumber || null,
|
|
12805
|
+
certification: practitionerDoc.data().certification
|
|
12806
|
+
} : null;
|
|
12807
|
+
let patientInfo = null;
|
|
12808
|
+
if (patientSensitiveInfoDoc.exists()) {
|
|
12809
|
+
const sensitiveData = patientSensitiveInfoDoc.data();
|
|
12810
|
+
patientInfo = {
|
|
12811
|
+
id: patientId,
|
|
12812
|
+
fullName: `${sensitiveData.firstName} ${sensitiveData.lastName}`,
|
|
12813
|
+
email: sensitiveData.email || "",
|
|
12814
|
+
phone: sensitiveData.phoneNumber || null,
|
|
12815
|
+
dateOfBirth: sensitiveData.dateOfBirth || Timestamp26.now(),
|
|
12816
|
+
gender: sensitiveData.gender || "other" /* OTHER */
|
|
12817
|
+
};
|
|
12818
|
+
} else if (patientDoc.exists()) {
|
|
12819
|
+
patientInfo = {
|
|
12820
|
+
id: patientDoc.id,
|
|
12821
|
+
fullName: patientDoc.data().displayName,
|
|
12822
|
+
email: ((_a = patientDoc.data().contactInfo) == null ? void 0 : _a.email) || "",
|
|
12823
|
+
phone: patientDoc.data().phoneNumber || null,
|
|
12824
|
+
dateOfBirth: patientDoc.data().dateOfBirth || Timestamp26.now(),
|
|
12825
|
+
gender: patientDoc.data().gender || "other" /* OTHER */
|
|
12826
|
+
};
|
|
12827
|
+
}
|
|
12828
|
+
return {
|
|
12829
|
+
clinicInfo,
|
|
12830
|
+
practitionerInfo,
|
|
12831
|
+
patientInfo
|
|
12832
|
+
};
|
|
12833
|
+
} catch (error) {
|
|
12834
|
+
console.error("Error fetching profile info cards:", error);
|
|
12835
|
+
return {
|
|
12836
|
+
clinicInfo: null,
|
|
12837
|
+
practitionerInfo: null,
|
|
12838
|
+
patientInfo: null
|
|
12839
|
+
};
|
|
12840
|
+
}
|
|
10349
12841
|
}
|
|
10350
|
-
|
|
12842
|
+
// #endregion
|
|
12843
|
+
};
|
|
10351
12844
|
|
|
10352
12845
|
// src/services/calendar/calendar.v3.service.ts
|
|
12846
|
+
import { Timestamp as Timestamp27, serverTimestamp as serverTimestamp21 } from "firebase/firestore";
|
|
12847
|
+
import { doc as doc24, getDoc as getDoc26, setDoc as setDoc22, updateDoc as updateDoc23, deleteDoc as deleteDoc14 } from "firebase/firestore";
|
|
10353
12848
|
var CalendarServiceV3 = class extends BaseService {
|
|
10354
12849
|
/**
|
|
10355
12850
|
* Creates a new CalendarServiceV3 instance
|
|
@@ -10373,7 +12868,7 @@ var CalendarServiceV3 = class extends BaseService {
|
|
|
10373
12868
|
params.entityType,
|
|
10374
12869
|
params.entityId
|
|
10375
12870
|
);
|
|
10376
|
-
const eventRef =
|
|
12871
|
+
const eventRef = doc24(this.db, collectionPath, eventId);
|
|
10377
12872
|
const eventData = {
|
|
10378
12873
|
id: eventId,
|
|
10379
12874
|
eventName: params.eventName,
|
|
@@ -10383,19 +12878,19 @@ var CalendarServiceV3 = class extends BaseService {
|
|
|
10383
12878
|
status: "confirmed" /* CONFIRMED */,
|
|
10384
12879
|
// Blocking events are always confirmed
|
|
10385
12880
|
syncStatus: "internal" /* INTERNAL */,
|
|
10386
|
-
createdAt:
|
|
10387
|
-
updatedAt:
|
|
12881
|
+
createdAt: serverTimestamp21(),
|
|
12882
|
+
updatedAt: serverTimestamp21()
|
|
10388
12883
|
};
|
|
10389
12884
|
if (params.entityType === "practitioner") {
|
|
10390
12885
|
eventData.practitionerProfileId = params.entityId;
|
|
10391
12886
|
} else {
|
|
10392
12887
|
eventData.clinicBranchId = params.entityId;
|
|
10393
12888
|
}
|
|
10394
|
-
await
|
|
12889
|
+
await setDoc22(eventRef, eventData);
|
|
10395
12890
|
return {
|
|
10396
12891
|
...eventData,
|
|
10397
|
-
createdAt:
|
|
10398
|
-
updatedAt:
|
|
12892
|
+
createdAt: Timestamp27.now(),
|
|
12893
|
+
updatedAt: Timestamp27.now()
|
|
10399
12894
|
};
|
|
10400
12895
|
}
|
|
10401
12896
|
/**
|
|
@@ -10408,13 +12903,13 @@ var CalendarServiceV3 = class extends BaseService {
|
|
|
10408
12903
|
params.entityType,
|
|
10409
12904
|
params.entityId
|
|
10410
12905
|
);
|
|
10411
|
-
const eventRef =
|
|
10412
|
-
const eventDoc = await
|
|
12906
|
+
const eventRef = doc24(this.db, collectionPath, params.eventId);
|
|
12907
|
+
const eventDoc = await getDoc26(eventRef);
|
|
10413
12908
|
if (!eventDoc.exists()) {
|
|
10414
12909
|
throw new Error(`Blocking event with ID ${params.eventId} not found`);
|
|
10415
12910
|
}
|
|
10416
12911
|
const updateData = {
|
|
10417
|
-
updatedAt:
|
|
12912
|
+
updatedAt: serverTimestamp21()
|
|
10418
12913
|
};
|
|
10419
12914
|
if (params.eventName !== void 0) {
|
|
10420
12915
|
updateData.eventName = params.eventName;
|
|
@@ -10428,8 +12923,8 @@ var CalendarServiceV3 = class extends BaseService {
|
|
|
10428
12923
|
if (params.status !== void 0) {
|
|
10429
12924
|
updateData.status = params.status;
|
|
10430
12925
|
}
|
|
10431
|
-
await
|
|
10432
|
-
const updatedEventDoc = await
|
|
12926
|
+
await updateDoc23(eventRef, updateData);
|
|
12927
|
+
const updatedEventDoc = await getDoc26(eventRef);
|
|
10433
12928
|
return updatedEventDoc.data();
|
|
10434
12929
|
}
|
|
10435
12930
|
/**
|
|
@@ -10440,12 +12935,12 @@ var CalendarServiceV3 = class extends BaseService {
|
|
|
10440
12935
|
*/
|
|
10441
12936
|
async deleteBlockingEvent(entityType, entityId, eventId) {
|
|
10442
12937
|
const collectionPath = this.getEntityCalendarPath(entityType, entityId);
|
|
10443
|
-
const eventRef =
|
|
10444
|
-
const eventDoc = await
|
|
12938
|
+
const eventRef = doc24(this.db, collectionPath, eventId);
|
|
12939
|
+
const eventDoc = await getDoc26(eventRef);
|
|
10445
12940
|
if (!eventDoc.exists()) {
|
|
10446
12941
|
throw new Error(`Blocking event with ID ${eventId} not found`);
|
|
10447
12942
|
}
|
|
10448
|
-
await
|
|
12943
|
+
await deleteDoc14(eventRef);
|
|
10449
12944
|
}
|
|
10450
12945
|
/**
|
|
10451
12946
|
* Gets a specific blocking event
|
|
@@ -10456,8 +12951,8 @@ var CalendarServiceV3 = class extends BaseService {
|
|
|
10456
12951
|
*/
|
|
10457
12952
|
async getBlockingEvent(entityType, entityId, eventId) {
|
|
10458
12953
|
const collectionPath = this.getEntityCalendarPath(entityType, entityId);
|
|
10459
|
-
const eventRef =
|
|
10460
|
-
const eventDoc = await
|
|
12954
|
+
const eventRef = doc24(this.db, collectionPath, eventId);
|
|
12955
|
+
const eventDoc = await getDoc26(eventRef);
|
|
10461
12956
|
if (!eventDoc.exists()) {
|
|
10462
12957
|
return null;
|
|
10463
12958
|
}
|
|
@@ -10651,18 +13146,18 @@ var ExternalCalendarService = class extends BaseService {
|
|
|
10651
13146
|
|
|
10652
13147
|
// src/services/clinic/practitioner-invite.service.ts
|
|
10653
13148
|
import {
|
|
10654
|
-
collection as
|
|
10655
|
-
doc as
|
|
10656
|
-
getDoc as
|
|
10657
|
-
getDocs as
|
|
10658
|
-
query as
|
|
10659
|
-
where as
|
|
10660
|
-
updateDoc as
|
|
10661
|
-
setDoc as
|
|
10662
|
-
deleteDoc as
|
|
10663
|
-
Timestamp as
|
|
10664
|
-
serverTimestamp as
|
|
10665
|
-
orderBy as
|
|
13149
|
+
collection as collection24,
|
|
13150
|
+
doc as doc25,
|
|
13151
|
+
getDoc as getDoc27,
|
|
13152
|
+
getDocs as getDocs24,
|
|
13153
|
+
query as query24,
|
|
13154
|
+
where as where24,
|
|
13155
|
+
updateDoc as updateDoc24,
|
|
13156
|
+
setDoc as setDoc23,
|
|
13157
|
+
deleteDoc as deleteDoc15,
|
|
13158
|
+
Timestamp as Timestamp28,
|
|
13159
|
+
serverTimestamp as serverTimestamp22,
|
|
13160
|
+
orderBy as orderBy12,
|
|
10666
13161
|
limit as limit11
|
|
10667
13162
|
} from "firebase/firestore";
|
|
10668
13163
|
var PractitionerInviteService = class extends BaseService {
|
|
@@ -10726,7 +13221,7 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10726
13221
|
message: message || null,
|
|
10727
13222
|
status: "pending" /* PENDING */
|
|
10728
13223
|
};
|
|
10729
|
-
const now =
|
|
13224
|
+
const now = Timestamp28.now();
|
|
10730
13225
|
const invite = {
|
|
10731
13226
|
id: inviteId,
|
|
10732
13227
|
...inviteData,
|
|
@@ -10737,8 +13232,8 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10737
13232
|
rejectedAt: null,
|
|
10738
13233
|
cancelledAt: null
|
|
10739
13234
|
};
|
|
10740
|
-
const docRef =
|
|
10741
|
-
await
|
|
13235
|
+
const docRef = doc25(this.db, PRACTITIONER_INVITES_COLLECTION, inviteId);
|
|
13236
|
+
await setDoc23(docRef, invite);
|
|
10742
13237
|
return invite;
|
|
10743
13238
|
} catch (error) {
|
|
10744
13239
|
console.error(
|
|
@@ -10757,18 +13252,18 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10757
13252
|
async getAllInvitesDoctor(practitionerId, statusFilter) {
|
|
10758
13253
|
try {
|
|
10759
13254
|
const constraints = [
|
|
10760
|
-
|
|
10761
|
-
|
|
13255
|
+
where24("practitionerId", "==", practitionerId),
|
|
13256
|
+
orderBy12("createdAt", "desc")
|
|
10762
13257
|
];
|
|
10763
13258
|
if (statusFilter && statusFilter.length > 0) {
|
|
10764
|
-
constraints.push(
|
|
13259
|
+
constraints.push(where24("status", "in", statusFilter));
|
|
10765
13260
|
}
|
|
10766
|
-
const q =
|
|
10767
|
-
|
|
13261
|
+
const q = query24(
|
|
13262
|
+
collection24(this.db, PRACTITIONER_INVITES_COLLECTION),
|
|
10768
13263
|
...constraints
|
|
10769
13264
|
);
|
|
10770
|
-
const querySnapshot = await
|
|
10771
|
-
return querySnapshot.docs.map((
|
|
13265
|
+
const querySnapshot = await getDocs24(q);
|
|
13266
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
10772
13267
|
} catch (error) {
|
|
10773
13268
|
console.error(
|
|
10774
13269
|
"[PractitionerInviteService] Error getting doctor invites:",
|
|
@@ -10786,18 +13281,18 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10786
13281
|
async getAllInvitesClinic(clinicId, statusFilter) {
|
|
10787
13282
|
try {
|
|
10788
13283
|
const constraints = [
|
|
10789
|
-
|
|
10790
|
-
|
|
13284
|
+
where24("clinicId", "==", clinicId),
|
|
13285
|
+
orderBy12("createdAt", "desc")
|
|
10791
13286
|
];
|
|
10792
13287
|
if (statusFilter && statusFilter.length > 0) {
|
|
10793
|
-
constraints.push(
|
|
13288
|
+
constraints.push(where24("status", "in", statusFilter));
|
|
10794
13289
|
}
|
|
10795
|
-
const q =
|
|
10796
|
-
|
|
13290
|
+
const q = query24(
|
|
13291
|
+
collection24(this.db, PRACTITIONER_INVITES_COLLECTION),
|
|
10797
13292
|
...constraints
|
|
10798
13293
|
);
|
|
10799
|
-
const querySnapshot = await
|
|
10800
|
-
return querySnapshot.docs.map((
|
|
13294
|
+
const querySnapshot = await getDocs24(q);
|
|
13295
|
+
return querySnapshot.docs.map((doc32) => doc32.data());
|
|
10801
13296
|
} catch (error) {
|
|
10802
13297
|
console.error(
|
|
10803
13298
|
"[PractitionerInviteService] Error getting clinic invites:",
|
|
@@ -10822,11 +13317,11 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10822
13317
|
}
|
|
10823
13318
|
const updateData = {
|
|
10824
13319
|
status: "accepted" /* ACCEPTED */,
|
|
10825
|
-
acceptedAt:
|
|
10826
|
-
updatedAt:
|
|
13320
|
+
acceptedAt: Timestamp28.now(),
|
|
13321
|
+
updatedAt: serverTimestamp22()
|
|
10827
13322
|
};
|
|
10828
|
-
const docRef =
|
|
10829
|
-
await
|
|
13323
|
+
const docRef = doc25(this.db, PRACTITIONER_INVITES_COLLECTION, inviteId);
|
|
13324
|
+
await updateDoc24(docRef, updateData);
|
|
10830
13325
|
return await this.getInviteById(inviteId);
|
|
10831
13326
|
} catch (error) {
|
|
10832
13327
|
console.error(
|
|
@@ -10854,11 +13349,11 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10854
13349
|
const updateData = {
|
|
10855
13350
|
status: "rejected" /* REJECTED */,
|
|
10856
13351
|
rejectionReason: rejectionReason || null,
|
|
10857
|
-
rejectedAt:
|
|
10858
|
-
updatedAt:
|
|
13352
|
+
rejectedAt: Timestamp28.now(),
|
|
13353
|
+
updatedAt: serverTimestamp22()
|
|
10859
13354
|
};
|
|
10860
|
-
const docRef =
|
|
10861
|
-
await
|
|
13355
|
+
const docRef = doc25(this.db, PRACTITIONER_INVITES_COLLECTION, inviteId);
|
|
13356
|
+
await updateDoc24(docRef, updateData);
|
|
10862
13357
|
return await this.getInviteById(inviteId);
|
|
10863
13358
|
} catch (error) {
|
|
10864
13359
|
console.error(
|
|
@@ -10886,11 +13381,11 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10886
13381
|
const updateData = {
|
|
10887
13382
|
status: "cancelled" /* CANCELLED */,
|
|
10888
13383
|
cancelReason: cancelReason || null,
|
|
10889
|
-
cancelledAt:
|
|
10890
|
-
updatedAt:
|
|
13384
|
+
cancelledAt: Timestamp28.now(),
|
|
13385
|
+
updatedAt: serverTimestamp22()
|
|
10891
13386
|
};
|
|
10892
|
-
const docRef =
|
|
10893
|
-
await
|
|
13387
|
+
const docRef = doc25(this.db, PRACTITIONER_INVITES_COLLECTION, inviteId);
|
|
13388
|
+
await updateDoc24(docRef, updateData);
|
|
10894
13389
|
return await this.getInviteById(inviteId);
|
|
10895
13390
|
} catch (error) {
|
|
10896
13391
|
console.error(
|
|
@@ -10907,8 +13402,8 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10907
13402
|
*/
|
|
10908
13403
|
async getInviteById(inviteId) {
|
|
10909
13404
|
try {
|
|
10910
|
-
const docRef =
|
|
10911
|
-
const docSnap = await
|
|
13405
|
+
const docRef = doc25(this.db, PRACTITIONER_INVITES_COLLECTION, inviteId);
|
|
13406
|
+
const docSnap = await getDoc27(docRef);
|
|
10912
13407
|
if (docSnap.exists()) {
|
|
10913
13408
|
return docSnap.data();
|
|
10914
13409
|
}
|
|
@@ -10930,30 +13425,30 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10930
13425
|
try {
|
|
10931
13426
|
const constraints = [];
|
|
10932
13427
|
if (filters.practitionerId) {
|
|
10933
|
-
constraints.push(
|
|
13428
|
+
constraints.push(where24("practitionerId", "==", filters.practitionerId));
|
|
10934
13429
|
}
|
|
10935
13430
|
if (filters.clinicId) {
|
|
10936
|
-
constraints.push(
|
|
13431
|
+
constraints.push(where24("clinicId", "==", filters.clinicId));
|
|
10937
13432
|
}
|
|
10938
13433
|
if (filters.invitedBy) {
|
|
10939
|
-
constraints.push(
|
|
13434
|
+
constraints.push(where24("invitedBy", "==", filters.invitedBy));
|
|
10940
13435
|
}
|
|
10941
13436
|
if (filters.status && filters.status.length > 0) {
|
|
10942
|
-
constraints.push(
|
|
13437
|
+
constraints.push(where24("status", "in", filters.status));
|
|
10943
13438
|
}
|
|
10944
13439
|
const orderField = filters.orderBy || "createdAt";
|
|
10945
13440
|
const orderDirection = filters.orderDirection || "desc";
|
|
10946
|
-
constraints.push(
|
|
13441
|
+
constraints.push(orderBy12(orderField, orderDirection));
|
|
10947
13442
|
if (filters.limit) {
|
|
10948
13443
|
constraints.push(limit11(filters.limit));
|
|
10949
13444
|
}
|
|
10950
|
-
const q =
|
|
10951
|
-
|
|
13445
|
+
const q = query24(
|
|
13446
|
+
collection24(this.db, PRACTITIONER_INVITES_COLLECTION),
|
|
10952
13447
|
...constraints
|
|
10953
13448
|
);
|
|
10954
|
-
const querySnapshot = await
|
|
13449
|
+
const querySnapshot = await getDocs24(q);
|
|
10955
13450
|
let invites = querySnapshot.docs.map(
|
|
10956
|
-
(
|
|
13451
|
+
(doc32) => doc32.data()
|
|
10957
13452
|
);
|
|
10958
13453
|
if (filters.fromDate) {
|
|
10959
13454
|
invites = invites.filter(
|
|
@@ -10980,8 +13475,8 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10980
13475
|
*/
|
|
10981
13476
|
async deleteInvite(inviteId) {
|
|
10982
13477
|
try {
|
|
10983
|
-
const docRef =
|
|
10984
|
-
await
|
|
13478
|
+
const docRef = doc25(this.db, PRACTITIONER_INVITES_COLLECTION, inviteId);
|
|
13479
|
+
await deleteDoc15(docRef);
|
|
10985
13480
|
} catch (error) {
|
|
10986
13481
|
console.error(
|
|
10987
13482
|
"[PractitionerInviteService] Error deleting invite:",
|
|
@@ -10998,8 +13493,8 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
10998
13493
|
*/
|
|
10999
13494
|
async getPractitionerById(practitionerId) {
|
|
11000
13495
|
try {
|
|
11001
|
-
const docRef =
|
|
11002
|
-
const docSnap = await
|
|
13496
|
+
const docRef = doc25(this.db, PRACTITIONERS_COLLECTION, practitionerId);
|
|
13497
|
+
const docSnap = await getDoc27(docRef);
|
|
11003
13498
|
return docSnap.exists() ? docSnap.data() : null;
|
|
11004
13499
|
} catch (error) {
|
|
11005
13500
|
console.error(
|
|
@@ -11016,8 +13511,8 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
11016
13511
|
*/
|
|
11017
13512
|
async getClinicById(clinicId) {
|
|
11018
13513
|
try {
|
|
11019
|
-
const docRef =
|
|
11020
|
-
const docSnap = await
|
|
13514
|
+
const docRef = doc25(this.db, CLINICS_COLLECTION, clinicId);
|
|
13515
|
+
const docSnap = await getDoc27(docRef);
|
|
11021
13516
|
return docSnap.exists() ? docSnap.data() : null;
|
|
11022
13517
|
} catch (error) {
|
|
11023
13518
|
console.error("[PractitionerInviteService] Error getting clinic:", error);
|
|
@@ -11032,14 +13527,14 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
11032
13527
|
*/
|
|
11033
13528
|
async findExistingInvite(practitionerId, clinicId) {
|
|
11034
13529
|
try {
|
|
11035
|
-
const q =
|
|
11036
|
-
|
|
11037
|
-
|
|
11038
|
-
|
|
11039
|
-
|
|
13530
|
+
const q = query24(
|
|
13531
|
+
collection24(this.db, PRACTITIONER_INVITES_COLLECTION),
|
|
13532
|
+
where24("practitionerId", "==", practitionerId),
|
|
13533
|
+
where24("clinicId", "==", clinicId),
|
|
13534
|
+
orderBy12("createdAt", "desc"),
|
|
11040
13535
|
limit11(1)
|
|
11041
13536
|
);
|
|
11042
|
-
const querySnapshot = await
|
|
13537
|
+
const querySnapshot = await getDocs24(q);
|
|
11043
13538
|
if (querySnapshot.empty) {
|
|
11044
13539
|
return null;
|
|
11045
13540
|
}
|
|
@@ -11056,23 +13551,23 @@ var PractitionerInviteService = class extends BaseService {
|
|
|
11056
13551
|
|
|
11057
13552
|
// src/services/documentation-templates/documentation-template.service.ts
|
|
11058
13553
|
import {
|
|
11059
|
-
collection as
|
|
11060
|
-
doc as
|
|
11061
|
-
getDoc as
|
|
11062
|
-
getDocs as
|
|
11063
|
-
setDoc as
|
|
11064
|
-
updateDoc as
|
|
11065
|
-
deleteDoc as
|
|
11066
|
-
query as
|
|
11067
|
-
where as
|
|
11068
|
-
orderBy as
|
|
13554
|
+
collection as collection25,
|
|
13555
|
+
doc as doc26,
|
|
13556
|
+
getDoc as getDoc28,
|
|
13557
|
+
getDocs as getDocs25,
|
|
13558
|
+
setDoc as setDoc24,
|
|
13559
|
+
updateDoc as updateDoc25,
|
|
13560
|
+
deleteDoc as deleteDoc16,
|
|
13561
|
+
query as query25,
|
|
13562
|
+
where as where25,
|
|
13563
|
+
orderBy as orderBy13,
|
|
11069
13564
|
limit as limit12,
|
|
11070
13565
|
startAfter as startAfter10
|
|
11071
13566
|
} from "firebase/firestore";
|
|
11072
13567
|
var DocumentationTemplateService = class extends BaseService {
|
|
11073
13568
|
constructor() {
|
|
11074
13569
|
super(...arguments);
|
|
11075
|
-
this.collectionRef =
|
|
13570
|
+
this.collectionRef = collection25(
|
|
11076
13571
|
this.db,
|
|
11077
13572
|
DOCUMENTATION_TEMPLATES_COLLECTION
|
|
11078
13573
|
);
|
|
@@ -11106,8 +13601,8 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11106
13601
|
isRequired: validatedData.isRequired || false,
|
|
11107
13602
|
sortingOrder: validatedData.sortingOrder || 0
|
|
11108
13603
|
};
|
|
11109
|
-
const docRef =
|
|
11110
|
-
await
|
|
13604
|
+
const docRef = doc26(this.collectionRef, templateId);
|
|
13605
|
+
await setDoc24(docRef, template);
|
|
11111
13606
|
return template;
|
|
11112
13607
|
}
|
|
11113
13608
|
/**
|
|
@@ -11117,8 +13612,8 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11117
13612
|
* @returns The template or null if not found
|
|
11118
13613
|
*/
|
|
11119
13614
|
async getTemplateById(templateId, version) {
|
|
11120
|
-
const docRef =
|
|
11121
|
-
const docSnap = await
|
|
13615
|
+
const docRef = doc26(this.collectionRef, templateId);
|
|
13616
|
+
const docSnap = await getDoc28(docRef);
|
|
11122
13617
|
if (!docSnap.exists()) {
|
|
11123
13618
|
return null;
|
|
11124
13619
|
}
|
|
@@ -11156,15 +13651,15 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11156
13651
|
if (!template) {
|
|
11157
13652
|
throw new Error(`Template with ID ${templateId} not found`);
|
|
11158
13653
|
}
|
|
11159
|
-
const versionsCollectionRef =
|
|
13654
|
+
const versionsCollectionRef = collection25(
|
|
11160
13655
|
this.db,
|
|
11161
13656
|
`${DOCUMENTATION_TEMPLATES_COLLECTION}/${templateId}/versions`
|
|
11162
13657
|
);
|
|
11163
|
-
const versionDocRef =
|
|
13658
|
+
const versionDocRef = doc26(
|
|
11164
13659
|
versionsCollectionRef,
|
|
11165
13660
|
template.version.toString()
|
|
11166
13661
|
);
|
|
11167
|
-
await
|
|
13662
|
+
await setDoc24(versionDocRef, template);
|
|
11168
13663
|
let updatedElements = template.elements;
|
|
11169
13664
|
if (validatedData.elements) {
|
|
11170
13665
|
updatedElements = validatedData.elements.map((element) => ({
|
|
@@ -11188,9 +13683,9 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11188
13683
|
updatePayload.isUserForm = (_a = validatedData.isUserForm) != null ? _a : false;
|
|
11189
13684
|
updatePayload.isRequired = (_b = validatedData.isRequired) != null ? _b : false;
|
|
11190
13685
|
updatePayload.sortingOrder = (_c = validatedData.sortingOrder) != null ? _c : 0;
|
|
11191
|
-
const docRef =
|
|
13686
|
+
const docRef = doc26(this.collectionRef, templateId);
|
|
11192
13687
|
console.log("Update payload", updatePayload);
|
|
11193
|
-
await
|
|
13688
|
+
await updateDoc25(docRef, updatePayload);
|
|
11194
13689
|
return { ...template, ...updatePayload };
|
|
11195
13690
|
}
|
|
11196
13691
|
/**
|
|
@@ -11200,11 +13695,11 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11200
13695
|
* @returns The template version or null if not found
|
|
11201
13696
|
*/
|
|
11202
13697
|
async getTemplateVersion(templateId, versionNumber) {
|
|
11203
|
-
const versionDocRef =
|
|
13698
|
+
const versionDocRef = doc26(
|
|
11204
13699
|
this.db,
|
|
11205
13700
|
`${DOCUMENTATION_TEMPLATES_COLLECTION}/${templateId}/versions/${versionNumber}`
|
|
11206
13701
|
);
|
|
11207
|
-
const versionDocSnap = await
|
|
13702
|
+
const versionDocSnap = await getDoc28(versionDocRef);
|
|
11208
13703
|
if (!versionDocSnap.exists()) {
|
|
11209
13704
|
return null;
|
|
11210
13705
|
}
|
|
@@ -11216,15 +13711,15 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11216
13711
|
* @returns Array of template versions
|
|
11217
13712
|
*/
|
|
11218
13713
|
async getTemplateOldVersions(templateId) {
|
|
11219
|
-
const versionsCollectionRef =
|
|
13714
|
+
const versionsCollectionRef = collection25(
|
|
11220
13715
|
this.db,
|
|
11221
13716
|
`${DOCUMENTATION_TEMPLATES_COLLECTION}/${templateId}/versions`
|
|
11222
13717
|
);
|
|
11223
|
-
const q =
|
|
11224
|
-
const querySnapshot = await
|
|
13718
|
+
const q = query25(versionsCollectionRef, orderBy13("version", "desc"));
|
|
13719
|
+
const querySnapshot = await getDocs25(q);
|
|
11225
13720
|
const versions = [];
|
|
11226
|
-
querySnapshot.forEach((
|
|
11227
|
-
versions.push(
|
|
13721
|
+
querySnapshot.forEach((doc32) => {
|
|
13722
|
+
versions.push(doc32.data());
|
|
11228
13723
|
});
|
|
11229
13724
|
return versions;
|
|
11230
13725
|
}
|
|
@@ -11233,8 +13728,8 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11233
13728
|
* @param templateId - ID of the template to delete
|
|
11234
13729
|
*/
|
|
11235
13730
|
async deleteTemplate(templateId) {
|
|
11236
|
-
const docRef =
|
|
11237
|
-
await
|
|
13731
|
+
const docRef = doc26(this.collectionRef, templateId);
|
|
13732
|
+
await deleteDoc16(docRef);
|
|
11238
13733
|
}
|
|
11239
13734
|
/**
|
|
11240
13735
|
* Get all active templates
|
|
@@ -11243,21 +13738,21 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11243
13738
|
* @returns Array of templates and the last document for pagination
|
|
11244
13739
|
*/
|
|
11245
13740
|
async getActiveTemplates(pageSize = 20, lastDoc) {
|
|
11246
|
-
let q =
|
|
13741
|
+
let q = query25(
|
|
11247
13742
|
this.collectionRef,
|
|
11248
|
-
|
|
11249
|
-
|
|
13743
|
+
where25("isActive", "==", true),
|
|
13744
|
+
orderBy13("updatedAt", "desc"),
|
|
11250
13745
|
limit12(pageSize)
|
|
11251
13746
|
);
|
|
11252
13747
|
if (lastDoc) {
|
|
11253
|
-
q =
|
|
13748
|
+
q = query25(q, startAfter10(lastDoc));
|
|
11254
13749
|
}
|
|
11255
|
-
const querySnapshot = await
|
|
13750
|
+
const querySnapshot = await getDocs25(q);
|
|
11256
13751
|
const templates = [];
|
|
11257
13752
|
let lastVisible = null;
|
|
11258
|
-
querySnapshot.forEach((
|
|
11259
|
-
templates.push(
|
|
11260
|
-
lastVisible =
|
|
13753
|
+
querySnapshot.forEach((doc32) => {
|
|
13754
|
+
templates.push(doc32.data());
|
|
13755
|
+
lastVisible = doc32;
|
|
11261
13756
|
});
|
|
11262
13757
|
return {
|
|
11263
13758
|
templates,
|
|
@@ -11272,22 +13767,22 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11272
13767
|
* @returns Array of templates and the last document for pagination
|
|
11273
13768
|
*/
|
|
11274
13769
|
async getTemplatesByTags(tags, pageSize = 20, lastDoc) {
|
|
11275
|
-
let q =
|
|
13770
|
+
let q = query25(
|
|
11276
13771
|
this.collectionRef,
|
|
11277
|
-
|
|
11278
|
-
|
|
11279
|
-
|
|
13772
|
+
where25("isActive", "==", true),
|
|
13773
|
+
where25("tags", "array-contains-any", tags),
|
|
13774
|
+
orderBy13("updatedAt", "desc"),
|
|
11280
13775
|
limit12(pageSize)
|
|
11281
13776
|
);
|
|
11282
13777
|
if (lastDoc) {
|
|
11283
|
-
q =
|
|
13778
|
+
q = query25(q, startAfter10(lastDoc));
|
|
11284
13779
|
}
|
|
11285
|
-
const querySnapshot = await
|
|
13780
|
+
const querySnapshot = await getDocs25(q);
|
|
11286
13781
|
const templates = [];
|
|
11287
13782
|
let lastVisible = null;
|
|
11288
|
-
querySnapshot.forEach((
|
|
11289
|
-
templates.push(
|
|
11290
|
-
lastVisible =
|
|
13783
|
+
querySnapshot.forEach((doc32) => {
|
|
13784
|
+
templates.push(doc32.data());
|
|
13785
|
+
lastVisible = doc32;
|
|
11291
13786
|
});
|
|
11292
13787
|
return {
|
|
11293
13788
|
templates,
|
|
@@ -11302,21 +13797,21 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11302
13797
|
* @returns Array of templates and the last document for pagination
|
|
11303
13798
|
*/
|
|
11304
13799
|
async getTemplatesByCreator(userId, pageSize = 20, lastDoc) {
|
|
11305
|
-
let q =
|
|
13800
|
+
let q = query25(
|
|
11306
13801
|
this.collectionRef,
|
|
11307
|
-
|
|
11308
|
-
|
|
13802
|
+
where25("createdBy", "==", userId),
|
|
13803
|
+
orderBy13("updatedAt", "desc"),
|
|
11309
13804
|
limit12(pageSize)
|
|
11310
13805
|
);
|
|
11311
13806
|
if (lastDoc) {
|
|
11312
|
-
q =
|
|
13807
|
+
q = query25(q, startAfter10(lastDoc));
|
|
11313
13808
|
}
|
|
11314
|
-
const querySnapshot = await
|
|
13809
|
+
const querySnapshot = await getDocs25(q);
|
|
11315
13810
|
const templates = [];
|
|
11316
13811
|
let lastVisible = null;
|
|
11317
|
-
querySnapshot.forEach((
|
|
11318
|
-
templates.push(
|
|
11319
|
-
lastVisible =
|
|
13812
|
+
querySnapshot.forEach((doc32) => {
|
|
13813
|
+
templates.push(doc32.data());
|
|
13814
|
+
lastVisible = doc32;
|
|
11320
13815
|
});
|
|
11321
13816
|
return {
|
|
11322
13817
|
templates,
|
|
@@ -11329,21 +13824,21 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11329
13824
|
* @returns Array of templates
|
|
11330
13825
|
*/
|
|
11331
13826
|
async getAllTemplatesForSelection(options) {
|
|
11332
|
-
let q =
|
|
13827
|
+
let q = query25(
|
|
11333
13828
|
this.collectionRef,
|
|
11334
|
-
|
|
11335
|
-
|
|
13829
|
+
where25("isActive", "==", true),
|
|
13830
|
+
orderBy13("updatedAt", "desc")
|
|
11336
13831
|
);
|
|
11337
13832
|
if ((options == null ? void 0 : options.isUserForm) !== void 0) {
|
|
11338
|
-
q =
|
|
13833
|
+
q = query25(q, where25("isUserForm", "==", options.isUserForm));
|
|
11339
13834
|
}
|
|
11340
13835
|
if ((options == null ? void 0 : options.isRequired) !== void 0) {
|
|
11341
|
-
q =
|
|
13836
|
+
q = query25(q, where25("isRequired", "==", options.isRequired));
|
|
11342
13837
|
}
|
|
11343
|
-
const querySnapshot = await
|
|
13838
|
+
const querySnapshot = await getDocs25(q);
|
|
11344
13839
|
const templates = [];
|
|
11345
|
-
querySnapshot.forEach((
|
|
11346
|
-
templates.push(
|
|
13840
|
+
querySnapshot.forEach((doc32) => {
|
|
13841
|
+
templates.push(doc32.data());
|
|
11347
13842
|
});
|
|
11348
13843
|
return templates;
|
|
11349
13844
|
}
|
|
@@ -11351,14 +13846,14 @@ var DocumentationTemplateService = class extends BaseService {
|
|
|
11351
13846
|
|
|
11352
13847
|
// src/services/documentation-templates/filled-document.service.ts
|
|
11353
13848
|
import {
|
|
11354
|
-
collection as
|
|
11355
|
-
doc as
|
|
11356
|
-
getDoc as
|
|
11357
|
-
getDocs as
|
|
11358
|
-
setDoc as
|
|
11359
|
-
updateDoc as
|
|
11360
|
-
query as
|
|
11361
|
-
orderBy as
|
|
13849
|
+
collection as collection26,
|
|
13850
|
+
doc as doc27,
|
|
13851
|
+
getDoc as getDoc29,
|
|
13852
|
+
getDocs as getDocs26,
|
|
13853
|
+
setDoc as setDoc25,
|
|
13854
|
+
updateDoc as updateDoc26,
|
|
13855
|
+
query as query26,
|
|
13856
|
+
orderBy as orderBy14,
|
|
11362
13857
|
limit as limit13,
|
|
11363
13858
|
startAfter as startAfter11
|
|
11364
13859
|
} from "firebase/firestore";
|
|
@@ -11416,7 +13911,7 @@ var FilledDocumentService = class extends BaseService {
|
|
|
11416
13911
|
values: initialValues,
|
|
11417
13912
|
status: initialStatus
|
|
11418
13913
|
};
|
|
11419
|
-
const docRef =
|
|
13914
|
+
const docRef = doc27(
|
|
11420
13915
|
this.db,
|
|
11421
13916
|
APPOINTMENTS_COLLECTION,
|
|
11422
13917
|
// Replaced "appointments"
|
|
@@ -11424,7 +13919,7 @@ var FilledDocumentService = class extends BaseService {
|
|
|
11424
13919
|
formSubcollection,
|
|
11425
13920
|
documentId3
|
|
11426
13921
|
);
|
|
11427
|
-
await
|
|
13922
|
+
await setDoc25(docRef, filledDocument);
|
|
11428
13923
|
return filledDocument;
|
|
11429
13924
|
}
|
|
11430
13925
|
/**
|
|
@@ -11436,7 +13931,7 @@ var FilledDocumentService = class extends BaseService {
|
|
|
11436
13931
|
*/
|
|
11437
13932
|
async getFilledDocumentFromAppointmentById(appointmentId, formId, isUserForm) {
|
|
11438
13933
|
const formSubcollection = this.getFormSubcollectionPath(isUserForm);
|
|
11439
|
-
const docRef =
|
|
13934
|
+
const docRef = doc27(
|
|
11440
13935
|
this.db,
|
|
11441
13936
|
APPOINTMENTS_COLLECTION,
|
|
11442
13937
|
// Replaced "appointments"
|
|
@@ -11444,7 +13939,7 @@ var FilledDocumentService = class extends BaseService {
|
|
|
11444
13939
|
formSubcollection,
|
|
11445
13940
|
formId
|
|
11446
13941
|
);
|
|
11447
|
-
const docSnap = await
|
|
13942
|
+
const docSnap = await getDoc29(docRef);
|
|
11448
13943
|
if (!docSnap.exists()) {
|
|
11449
13944
|
return null;
|
|
11450
13945
|
}
|
|
@@ -11461,7 +13956,7 @@ var FilledDocumentService = class extends BaseService {
|
|
|
11461
13956
|
*/
|
|
11462
13957
|
async updateFilledDocumentInAppointment(appointmentId, formId, isUserForm, values, status) {
|
|
11463
13958
|
const formSubcollection = this.getFormSubcollectionPath(isUserForm);
|
|
11464
|
-
const docRef =
|
|
13959
|
+
const docRef = doc27(
|
|
11465
13960
|
this.db,
|
|
11466
13961
|
APPOINTMENTS_COLLECTION,
|
|
11467
13962
|
// Replaced "appointments"
|
|
@@ -11493,7 +13988,7 @@ var FilledDocumentService = class extends BaseService {
|
|
|
11493
13988
|
}
|
|
11494
13989
|
if (Object.keys(updatePayload).length === 1 && "updatedAt" in updatePayload) {
|
|
11495
13990
|
}
|
|
11496
|
-
await
|
|
13991
|
+
await updateDoc26(docRef, updatePayload);
|
|
11497
13992
|
return { ...existingDoc, ...updatePayload };
|
|
11498
13993
|
}
|
|
11499
13994
|
/**
|
|
@@ -11503,20 +13998,20 @@ var FilledDocumentService = class extends BaseService {
|
|
|
11503
13998
|
* @param lastDoc Last document from previous page for pagination.
|
|
11504
13999
|
*/
|
|
11505
14000
|
async getFilledUserFormsForAppointment(appointmentId, pageSize = 20, lastDoc) {
|
|
11506
|
-
const subcollectionRef =
|
|
14001
|
+
const subcollectionRef = collection26(
|
|
11507
14002
|
this.db,
|
|
11508
14003
|
APPOINTMENTS_COLLECTION,
|
|
11509
14004
|
// Replaced "appointments"
|
|
11510
14005
|
appointmentId,
|
|
11511
14006
|
USER_FORMS_SUBCOLLECTION
|
|
11512
14007
|
);
|
|
11513
|
-
let q =
|
|
14008
|
+
let q = query26(
|
|
11514
14009
|
subcollectionRef,
|
|
11515
|
-
|
|
14010
|
+
orderBy14("updatedAt", "desc"),
|
|
11516
14011
|
limit13(pageSize)
|
|
11517
14012
|
);
|
|
11518
14013
|
if (lastDoc) {
|
|
11519
|
-
q =
|
|
14014
|
+
q = query26(q, startAfter11(lastDoc));
|
|
11520
14015
|
}
|
|
11521
14016
|
return this.executeQuery(q);
|
|
11522
14017
|
}
|
|
@@ -11527,31 +14022,31 @@ var FilledDocumentService = class extends BaseService {
|
|
|
11527
14022
|
* @param lastDoc Last document from previous page for pagination.
|
|
11528
14023
|
*/
|
|
11529
14024
|
async getFilledDoctorFormsForAppointment(appointmentId, pageSize = 20, lastDoc) {
|
|
11530
|
-
const subcollectionRef =
|
|
14025
|
+
const subcollectionRef = collection26(
|
|
11531
14026
|
this.db,
|
|
11532
14027
|
APPOINTMENTS_COLLECTION,
|
|
11533
14028
|
// Replaced "appointments"
|
|
11534
14029
|
appointmentId,
|
|
11535
14030
|
DOCTOR_FORMS_SUBCOLLECTION
|
|
11536
14031
|
);
|
|
11537
|
-
let q =
|
|
14032
|
+
let q = query26(
|
|
11538
14033
|
subcollectionRef,
|
|
11539
|
-
|
|
14034
|
+
orderBy14("updatedAt", "desc"),
|
|
11540
14035
|
limit13(pageSize)
|
|
11541
14036
|
);
|
|
11542
14037
|
if (lastDoc) {
|
|
11543
|
-
q =
|
|
14038
|
+
q = query26(q, startAfter11(lastDoc));
|
|
11544
14039
|
}
|
|
11545
14040
|
return this.executeQuery(q);
|
|
11546
14041
|
}
|
|
11547
14042
|
// Helper to execute query and return documents + lastDoc
|
|
11548
14043
|
async executeQuery(q) {
|
|
11549
|
-
const querySnapshot = await
|
|
14044
|
+
const querySnapshot = await getDocs26(q);
|
|
11550
14045
|
const documents = [];
|
|
11551
14046
|
let lastVisible = null;
|
|
11552
|
-
querySnapshot.forEach((
|
|
11553
|
-
documents.push(
|
|
11554
|
-
lastVisible =
|
|
14047
|
+
querySnapshot.forEach((doc32) => {
|
|
14048
|
+
documents.push(doc32.data());
|
|
14049
|
+
lastVisible = doc32;
|
|
11555
14050
|
});
|
|
11556
14051
|
return {
|
|
11557
14052
|
documents,
|
|
@@ -11711,16 +14206,16 @@ var FilledDocumentService = class extends BaseService {
|
|
|
11711
14206
|
|
|
11712
14207
|
// src/services/notifications/notification.service.ts
|
|
11713
14208
|
import {
|
|
11714
|
-
collection as
|
|
11715
|
-
doc as
|
|
11716
|
-
getDoc as
|
|
11717
|
-
getDocs as
|
|
11718
|
-
query as
|
|
11719
|
-
where as
|
|
11720
|
-
updateDoc as
|
|
11721
|
-
deleteDoc as
|
|
11722
|
-
orderBy as
|
|
11723
|
-
Timestamp as
|
|
14209
|
+
collection as collection27,
|
|
14210
|
+
doc as doc28,
|
|
14211
|
+
getDoc as getDoc30,
|
|
14212
|
+
getDocs as getDocs27,
|
|
14213
|
+
query as query27,
|
|
14214
|
+
where as where27,
|
|
14215
|
+
updateDoc as updateDoc27,
|
|
14216
|
+
deleteDoc as deleteDoc17,
|
|
14217
|
+
orderBy as orderBy15,
|
|
14218
|
+
Timestamp as Timestamp30,
|
|
11724
14219
|
addDoc as addDoc2,
|
|
11725
14220
|
writeBatch as writeBatch5
|
|
11726
14221
|
} from "firebase/firestore";
|
|
@@ -11729,8 +14224,8 @@ var NotificationService = class extends BaseService {
|
|
|
11729
14224
|
* Kreira novu notifikaciju
|
|
11730
14225
|
*/
|
|
11731
14226
|
async createNotification(notification) {
|
|
11732
|
-
const notificationsRef =
|
|
11733
|
-
const now =
|
|
14227
|
+
const notificationsRef = collection27(this.db, NOTIFICATIONS_COLLECTION);
|
|
14228
|
+
const now = Timestamp30.now();
|
|
11734
14229
|
const notificationData = {
|
|
11735
14230
|
...notification,
|
|
11736
14231
|
createdAt: now,
|
|
@@ -11749,12 +14244,12 @@ var NotificationService = class extends BaseService {
|
|
|
11749
14244
|
* Dohvata notifikaciju po ID-u
|
|
11750
14245
|
*/
|
|
11751
14246
|
async getNotification(notificationId) {
|
|
11752
|
-
const notificationRef =
|
|
14247
|
+
const notificationRef = doc28(
|
|
11753
14248
|
this.db,
|
|
11754
14249
|
NOTIFICATIONS_COLLECTION,
|
|
11755
14250
|
notificationId
|
|
11756
14251
|
);
|
|
11757
|
-
const notificationDoc = await
|
|
14252
|
+
const notificationDoc = await getDoc30(notificationRef);
|
|
11758
14253
|
if (!notificationDoc.exists()) {
|
|
11759
14254
|
return null;
|
|
11760
14255
|
}
|
|
@@ -11767,45 +14262,45 @@ var NotificationService = class extends BaseService {
|
|
|
11767
14262
|
* Dohvata sve notifikacije za korisnika
|
|
11768
14263
|
*/
|
|
11769
14264
|
async getUserNotifications(userId) {
|
|
11770
|
-
const q =
|
|
11771
|
-
|
|
11772
|
-
|
|
11773
|
-
|
|
11774
|
-
);
|
|
11775
|
-
const querySnapshot = await
|
|
11776
|
-
return querySnapshot.docs.map((
|
|
11777
|
-
id:
|
|
11778
|
-
...
|
|
14265
|
+
const q = query27(
|
|
14266
|
+
collection27(this.db, NOTIFICATIONS_COLLECTION),
|
|
14267
|
+
where27("userId", "==", userId),
|
|
14268
|
+
orderBy15("notificationTime", "desc")
|
|
14269
|
+
);
|
|
14270
|
+
const querySnapshot = await getDocs27(q);
|
|
14271
|
+
return querySnapshot.docs.map((doc32) => ({
|
|
14272
|
+
id: doc32.id,
|
|
14273
|
+
...doc32.data()
|
|
11779
14274
|
}));
|
|
11780
14275
|
}
|
|
11781
14276
|
/**
|
|
11782
14277
|
* Dohvata nepročitane notifikacije za korisnika
|
|
11783
14278
|
*/
|
|
11784
14279
|
async getUnreadNotifications(userId) {
|
|
11785
|
-
const q =
|
|
11786
|
-
|
|
11787
|
-
|
|
11788
|
-
|
|
11789
|
-
|
|
11790
|
-
);
|
|
11791
|
-
const querySnapshot = await
|
|
11792
|
-
return querySnapshot.docs.map((
|
|
11793
|
-
id:
|
|
11794
|
-
...
|
|
14280
|
+
const q = query27(
|
|
14281
|
+
collection27(this.db, NOTIFICATIONS_COLLECTION),
|
|
14282
|
+
where27("userId", "==", userId),
|
|
14283
|
+
where27("isRead", "==", false),
|
|
14284
|
+
orderBy15("notificationTime", "desc")
|
|
14285
|
+
);
|
|
14286
|
+
const querySnapshot = await getDocs27(q);
|
|
14287
|
+
return querySnapshot.docs.map((doc32) => ({
|
|
14288
|
+
id: doc32.id,
|
|
14289
|
+
...doc32.data()
|
|
11795
14290
|
}));
|
|
11796
14291
|
}
|
|
11797
14292
|
/**
|
|
11798
14293
|
* Označava notifikaciju kao pročitanu
|
|
11799
14294
|
*/
|
|
11800
14295
|
async markAsRead(notificationId) {
|
|
11801
|
-
const notificationRef =
|
|
14296
|
+
const notificationRef = doc28(
|
|
11802
14297
|
this.db,
|
|
11803
14298
|
NOTIFICATIONS_COLLECTION,
|
|
11804
14299
|
notificationId
|
|
11805
14300
|
);
|
|
11806
|
-
await
|
|
14301
|
+
await updateDoc27(notificationRef, {
|
|
11807
14302
|
isRead: true,
|
|
11808
|
-
updatedAt:
|
|
14303
|
+
updatedAt: Timestamp30.now()
|
|
11809
14304
|
});
|
|
11810
14305
|
}
|
|
11811
14306
|
/**
|
|
@@ -11815,14 +14310,14 @@ var NotificationService = class extends BaseService {
|
|
|
11815
14310
|
const notifications = await this.getUnreadNotifications(userId);
|
|
11816
14311
|
const batch = writeBatch5(this.db);
|
|
11817
14312
|
notifications.forEach((notification) => {
|
|
11818
|
-
const notificationRef =
|
|
14313
|
+
const notificationRef = doc28(
|
|
11819
14314
|
this.db,
|
|
11820
14315
|
NOTIFICATIONS_COLLECTION,
|
|
11821
14316
|
notification.id
|
|
11822
14317
|
);
|
|
11823
14318
|
batch.update(notificationRef, {
|
|
11824
14319
|
isRead: true,
|
|
11825
|
-
updatedAt:
|
|
14320
|
+
updatedAt: Timestamp30.now()
|
|
11826
14321
|
});
|
|
11827
14322
|
});
|
|
11828
14323
|
await batch.commit();
|
|
@@ -11831,86 +14326,86 @@ var NotificationService = class extends BaseService {
|
|
|
11831
14326
|
* Ažurira status notifikacije
|
|
11832
14327
|
*/
|
|
11833
14328
|
async updateNotificationStatus(notificationId, status) {
|
|
11834
|
-
const notificationRef =
|
|
14329
|
+
const notificationRef = doc28(
|
|
11835
14330
|
this.db,
|
|
11836
14331
|
NOTIFICATIONS_COLLECTION,
|
|
11837
14332
|
notificationId
|
|
11838
14333
|
);
|
|
11839
|
-
await
|
|
14334
|
+
await updateDoc27(notificationRef, {
|
|
11840
14335
|
status,
|
|
11841
|
-
updatedAt:
|
|
14336
|
+
updatedAt: Timestamp30.now()
|
|
11842
14337
|
});
|
|
11843
14338
|
}
|
|
11844
14339
|
/**
|
|
11845
14340
|
* Briše notifikaciju
|
|
11846
14341
|
*/
|
|
11847
14342
|
async deleteNotification(notificationId) {
|
|
11848
|
-
const notificationRef =
|
|
14343
|
+
const notificationRef = doc28(
|
|
11849
14344
|
this.db,
|
|
11850
14345
|
NOTIFICATIONS_COLLECTION,
|
|
11851
14346
|
notificationId
|
|
11852
14347
|
);
|
|
11853
|
-
await
|
|
14348
|
+
await deleteDoc17(notificationRef);
|
|
11854
14349
|
}
|
|
11855
14350
|
/**
|
|
11856
14351
|
* Dohvata notifikacije po tipu
|
|
11857
14352
|
*/
|
|
11858
14353
|
async getNotificationsByType(userId, type) {
|
|
11859
|
-
const q =
|
|
11860
|
-
|
|
11861
|
-
|
|
11862
|
-
|
|
11863
|
-
|
|
11864
|
-
);
|
|
11865
|
-
const querySnapshot = await
|
|
11866
|
-
return querySnapshot.docs.map((
|
|
11867
|
-
id:
|
|
11868
|
-
...
|
|
14354
|
+
const q = query27(
|
|
14355
|
+
collection27(this.db, NOTIFICATIONS_COLLECTION),
|
|
14356
|
+
where27("userId", "==", userId),
|
|
14357
|
+
where27("notificationType", "==", type),
|
|
14358
|
+
orderBy15("notificationTime", "desc")
|
|
14359
|
+
);
|
|
14360
|
+
const querySnapshot = await getDocs27(q);
|
|
14361
|
+
return querySnapshot.docs.map((doc32) => ({
|
|
14362
|
+
id: doc32.id,
|
|
14363
|
+
...doc32.data()
|
|
11869
14364
|
}));
|
|
11870
14365
|
}
|
|
11871
14366
|
/**
|
|
11872
14367
|
* Dohvata notifikacije za određeni termin
|
|
11873
14368
|
*/
|
|
11874
14369
|
async getAppointmentNotifications(appointmentId) {
|
|
11875
|
-
const q =
|
|
11876
|
-
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
);
|
|
11880
|
-
const querySnapshot = await
|
|
11881
|
-
return querySnapshot.docs.map((
|
|
11882
|
-
id:
|
|
11883
|
-
...
|
|
14370
|
+
const q = query27(
|
|
14371
|
+
collection27(this.db, NOTIFICATIONS_COLLECTION),
|
|
14372
|
+
where27("appointmentId", "==", appointmentId),
|
|
14373
|
+
orderBy15("notificationTime", "desc")
|
|
14374
|
+
);
|
|
14375
|
+
const querySnapshot = await getDocs27(q);
|
|
14376
|
+
return querySnapshot.docs.map((doc32) => ({
|
|
14377
|
+
id: doc32.id,
|
|
14378
|
+
...doc32.data()
|
|
11884
14379
|
}));
|
|
11885
14380
|
}
|
|
11886
14381
|
};
|
|
11887
14382
|
|
|
11888
14383
|
// src/services/patient/patientRequirements.service.ts
|
|
11889
14384
|
import {
|
|
11890
|
-
collection as
|
|
11891
|
-
getDocs as
|
|
11892
|
-
query as
|
|
11893
|
-
where as
|
|
11894
|
-
doc as
|
|
11895
|
-
updateDoc as
|
|
11896
|
-
Timestamp as
|
|
11897
|
-
orderBy as
|
|
14385
|
+
collection as collection28,
|
|
14386
|
+
getDocs as getDocs28,
|
|
14387
|
+
query as query28,
|
|
14388
|
+
where as where28,
|
|
14389
|
+
doc as doc29,
|
|
14390
|
+
updateDoc as updateDoc28,
|
|
14391
|
+
Timestamp as Timestamp31,
|
|
14392
|
+
orderBy as orderBy16,
|
|
11898
14393
|
limit as limit14,
|
|
11899
14394
|
startAfter as startAfter12,
|
|
11900
|
-
getDoc as
|
|
14395
|
+
getDoc as getDoc31
|
|
11901
14396
|
} from "firebase/firestore";
|
|
11902
14397
|
var PatientRequirementsService = class extends BaseService {
|
|
11903
14398
|
constructor(db, auth, app) {
|
|
11904
14399
|
super(db, auth, app);
|
|
11905
14400
|
}
|
|
11906
14401
|
getPatientRequirementsCollectionRef(patientId) {
|
|
11907
|
-
return
|
|
14402
|
+
return collection28(
|
|
11908
14403
|
this.db,
|
|
11909
14404
|
`patients/${patientId}/${PATIENT_REQUIREMENTS_SUBCOLLECTION_NAME}`
|
|
11910
14405
|
);
|
|
11911
14406
|
}
|
|
11912
14407
|
getPatientRequirementDocRef(patientId, instanceId) {
|
|
11913
|
-
return
|
|
14408
|
+
return doc29(
|
|
11914
14409
|
this.getPatientRequirementsCollectionRef(patientId),
|
|
11915
14410
|
instanceId
|
|
11916
14411
|
);
|
|
@@ -11923,7 +14418,7 @@ var PatientRequirementsService = class extends BaseService {
|
|
|
11923
14418
|
*/
|
|
11924
14419
|
async getPatientRequirementInstance(patientId, instanceId) {
|
|
11925
14420
|
const docRef = this.getPatientRequirementDocRef(patientId, instanceId);
|
|
11926
|
-
const docSnap = await
|
|
14421
|
+
const docSnap = await getDoc31(docRef);
|
|
11927
14422
|
if (!docSnap.exists()) {
|
|
11928
14423
|
return null;
|
|
11929
14424
|
}
|
|
@@ -11942,22 +14437,22 @@ var PatientRequirementsService = class extends BaseService {
|
|
|
11942
14437
|
*/
|
|
11943
14438
|
async getAllPatientRequirementInstances(patientId, filters, pageLimit = 20, lastVisible) {
|
|
11944
14439
|
const collRef = this.getPatientRequirementsCollectionRef(patientId);
|
|
11945
|
-
let q =
|
|
14440
|
+
let q = query28(collRef, orderBy16("createdAt", "desc"));
|
|
11946
14441
|
const queryConstraints = [];
|
|
11947
14442
|
if ((filters == null ? void 0 : filters.appointmentId) && filters.appointmentId !== "all") {
|
|
11948
14443
|
queryConstraints.push(
|
|
11949
|
-
|
|
14444
|
+
where28("appointmentId", "==", filters.appointmentId)
|
|
11950
14445
|
);
|
|
11951
14446
|
}
|
|
11952
14447
|
if ((filters == null ? void 0 : filters.statuses) && filters.statuses.length > 0) {
|
|
11953
|
-
queryConstraints.push(
|
|
14448
|
+
queryConstraints.push(where28("overallStatus", "in", filters.statuses));
|
|
11954
14449
|
}
|
|
11955
14450
|
if (lastVisible) {
|
|
11956
14451
|
queryConstraints.push(startAfter12(lastVisible));
|
|
11957
14452
|
}
|
|
11958
14453
|
queryConstraints.push(limit14(pageLimit));
|
|
11959
|
-
q =
|
|
11960
|
-
const snapshot = await
|
|
14454
|
+
q = query28(collRef, ...queryConstraints);
|
|
14455
|
+
const snapshot = await getDocs28(q);
|
|
11961
14456
|
let requirements = snapshot.docs.map((docSnap) => {
|
|
11962
14457
|
const data = docSnap.data();
|
|
11963
14458
|
return { id: docSnap.id, ...data };
|
|
@@ -12000,7 +14495,7 @@ var PatientRequirementsService = class extends BaseService {
|
|
|
12000
14495
|
*/
|
|
12001
14496
|
async completeInstruction(patientId, instanceId, instructionId) {
|
|
12002
14497
|
const instanceRef = this.getPatientRequirementDocRef(patientId, instanceId);
|
|
12003
|
-
const instanceSnap = await
|
|
14498
|
+
const instanceSnap = await getDoc31(instanceRef);
|
|
12004
14499
|
if (!instanceSnap.exists()) {
|
|
12005
14500
|
throw new Error(
|
|
12006
14501
|
`PatientRequirementInstance ${instanceId} not found for patient ${patientId}.`
|
|
@@ -12028,7 +14523,7 @@ var PatientRequirementsService = class extends BaseService {
|
|
|
12028
14523
|
`Instruction ${instructionId} is in status ${instructionToUpdate.status} and cannot be marked as completed.`
|
|
12029
14524
|
);
|
|
12030
14525
|
}
|
|
12031
|
-
const now =
|
|
14526
|
+
const now = Timestamp31.now();
|
|
12032
14527
|
const updatedInstructions = [...instance.instructions];
|
|
12033
14528
|
updatedInstructions[instructionIndex] = {
|
|
12034
14529
|
...instructionToUpdate,
|
|
@@ -12055,7 +14550,7 @@ var PatientRequirementsService = class extends BaseService {
|
|
|
12055
14550
|
if (newOverallStatus !== instance.overallStatus) {
|
|
12056
14551
|
updatePayload.overallStatus = newOverallStatus;
|
|
12057
14552
|
}
|
|
12058
|
-
await
|
|
14553
|
+
await updateDoc28(instanceRef, updatePayload);
|
|
12059
14554
|
return {
|
|
12060
14555
|
...instance,
|
|
12061
14556
|
instructions: updatedInstructions,
|
|
@@ -12069,18 +14564,18 @@ var PatientRequirementsService = class extends BaseService {
|
|
|
12069
14564
|
|
|
12070
14565
|
// src/services/procedure/procedure.service.ts
|
|
12071
14566
|
import {
|
|
12072
|
-
collection as
|
|
12073
|
-
doc as
|
|
12074
|
-
getDoc as
|
|
12075
|
-
getDocs as
|
|
12076
|
-
query as
|
|
12077
|
-
where as
|
|
12078
|
-
updateDoc as
|
|
12079
|
-
setDoc as
|
|
12080
|
-
deleteDoc as
|
|
12081
|
-
serverTimestamp as
|
|
14567
|
+
collection as collection29,
|
|
14568
|
+
doc as doc30,
|
|
14569
|
+
getDoc as getDoc32,
|
|
14570
|
+
getDocs as getDocs29,
|
|
14571
|
+
query as query29,
|
|
14572
|
+
where as where29,
|
|
14573
|
+
updateDoc as updateDoc29,
|
|
14574
|
+
setDoc as setDoc26,
|
|
14575
|
+
deleteDoc as deleteDoc18,
|
|
14576
|
+
serverTimestamp as serverTimestamp25,
|
|
12082
14577
|
writeBatch as writeBatch6,
|
|
12083
|
-
orderBy as
|
|
14578
|
+
orderBy as orderBy17,
|
|
12084
14579
|
limit as limit15,
|
|
12085
14580
|
startAfter as startAfter13,
|
|
12086
14581
|
documentId as documentId2
|
|
@@ -12239,24 +14734,24 @@ var ProcedureService = class extends BaseService {
|
|
|
12239
14734
|
if (!category || !subcategory || !technology || !product) {
|
|
12240
14735
|
throw new Error("One or more required base entities not found");
|
|
12241
14736
|
}
|
|
12242
|
-
const clinicRef =
|
|
14737
|
+
const clinicRef = doc30(
|
|
12243
14738
|
this.db,
|
|
12244
14739
|
CLINICS_COLLECTION,
|
|
12245
14740
|
validatedData.clinicBranchId
|
|
12246
14741
|
);
|
|
12247
|
-
const clinicSnapshot = await
|
|
14742
|
+
const clinicSnapshot = await getDoc32(clinicRef);
|
|
12248
14743
|
if (!clinicSnapshot.exists()) {
|
|
12249
14744
|
throw new Error(
|
|
12250
14745
|
`Clinic with ID ${validatedData.clinicBranchId} not found`
|
|
12251
14746
|
);
|
|
12252
14747
|
}
|
|
12253
14748
|
const clinic = clinicSnapshot.data();
|
|
12254
|
-
const practitionerRef =
|
|
14749
|
+
const practitionerRef = doc30(
|
|
12255
14750
|
this.db,
|
|
12256
14751
|
PRACTITIONERS_COLLECTION,
|
|
12257
14752
|
validatedData.practitionerId
|
|
12258
14753
|
);
|
|
12259
|
-
const practitionerSnapshot = await
|
|
14754
|
+
const practitionerSnapshot = await getDoc32(practitionerRef);
|
|
12260
14755
|
if (!practitionerSnapshot.exists()) {
|
|
12261
14756
|
throw new Error(
|
|
12262
14757
|
`Practitioner with ID ${validatedData.practitionerId} not found`
|
|
@@ -12322,13 +14817,13 @@ var ProcedureService = class extends BaseService {
|
|
|
12322
14817
|
isActive: true
|
|
12323
14818
|
// Default to active
|
|
12324
14819
|
};
|
|
12325
|
-
const procedureRef =
|
|
12326
|
-
await
|
|
14820
|
+
const procedureRef = doc30(this.db, PROCEDURES_COLLECTION, procedureId);
|
|
14821
|
+
await setDoc26(procedureRef, {
|
|
12327
14822
|
...newProcedure,
|
|
12328
|
-
createdAt:
|
|
12329
|
-
updatedAt:
|
|
14823
|
+
createdAt: serverTimestamp25(),
|
|
14824
|
+
updatedAt: serverTimestamp25()
|
|
12330
14825
|
});
|
|
12331
|
-
const savedDoc = await
|
|
14826
|
+
const savedDoc = await getDoc32(procedureRef);
|
|
12332
14827
|
return savedDoc.data();
|
|
12333
14828
|
}
|
|
12334
14829
|
/**
|
|
@@ -12357,7 +14852,7 @@ var ProcedureService = class extends BaseService {
|
|
|
12357
14852
|
validatedData.technologyId,
|
|
12358
14853
|
validatedData.productId
|
|
12359
14854
|
),
|
|
12360
|
-
|
|
14855
|
+
getDoc32(doc30(this.db, CLINICS_COLLECTION, validatedData.clinicBranchId))
|
|
12361
14856
|
]);
|
|
12362
14857
|
if (!category || !subcategory || !technology || !product) {
|
|
12363
14858
|
throw new Error("One or more required base entities not found");
|
|
@@ -12380,13 +14875,13 @@ var ProcedureService = class extends BaseService {
|
|
|
12380
14875
|
const practitionersMap = /* @__PURE__ */ new Map();
|
|
12381
14876
|
for (let i = 0; i < practitionerIds.length; i += 30) {
|
|
12382
14877
|
const chunk = practitionerIds.slice(i, i + 30);
|
|
12383
|
-
const practitionersQuery =
|
|
12384
|
-
|
|
12385
|
-
|
|
14878
|
+
const practitionersQuery = query29(
|
|
14879
|
+
collection29(this.db, PRACTITIONERS_COLLECTION),
|
|
14880
|
+
where29(documentId2(), "in", chunk)
|
|
12386
14881
|
);
|
|
12387
|
-
const practitionersSnapshot = await
|
|
12388
|
-
practitionersSnapshot.docs.forEach((
|
|
12389
|
-
practitionersMap.set(
|
|
14882
|
+
const practitionersSnapshot = await getDocs29(practitionersQuery);
|
|
14883
|
+
practitionersSnapshot.docs.forEach((doc32) => {
|
|
14884
|
+
practitionersMap.set(doc32.id, doc32.data());
|
|
12390
14885
|
});
|
|
12391
14886
|
}
|
|
12392
14887
|
if (practitionersMap.size !== practitionerIds.length) {
|
|
@@ -12420,7 +14915,7 @@ var ProcedureService = class extends BaseService {
|
|
|
12420
14915
|
};
|
|
12421
14916
|
const procedureId = this.generateId();
|
|
12422
14917
|
createdProcedureIds.push(procedureId);
|
|
12423
|
-
const procedureRef =
|
|
14918
|
+
const procedureRef = doc30(this.db, PROCEDURES_COLLECTION, procedureId);
|
|
12424
14919
|
const newProcedure = {
|
|
12425
14920
|
id: procedureId,
|
|
12426
14921
|
...validatedData,
|
|
@@ -12455,21 +14950,21 @@ var ProcedureService = class extends BaseService {
|
|
|
12455
14950
|
};
|
|
12456
14951
|
batch.set(procedureRef, {
|
|
12457
14952
|
...newProcedure,
|
|
12458
|
-
createdAt:
|
|
12459
|
-
updatedAt:
|
|
14953
|
+
createdAt: serverTimestamp25(),
|
|
14954
|
+
updatedAt: serverTimestamp25()
|
|
12460
14955
|
});
|
|
12461
14956
|
}
|
|
12462
14957
|
await batch.commit();
|
|
12463
14958
|
const fetchedProcedures = [];
|
|
12464
14959
|
for (let i = 0; i < createdProcedureIds.length; i += 30) {
|
|
12465
14960
|
const chunk = createdProcedureIds.slice(i, i + 30);
|
|
12466
|
-
const q =
|
|
12467
|
-
|
|
12468
|
-
|
|
14961
|
+
const q = query29(
|
|
14962
|
+
collection29(this.db, PROCEDURES_COLLECTION),
|
|
14963
|
+
where29(documentId2(), "in", chunk)
|
|
12469
14964
|
);
|
|
12470
|
-
const snapshot = await
|
|
12471
|
-
snapshot.forEach((
|
|
12472
|
-
fetchedProcedures.push(
|
|
14965
|
+
const snapshot = await getDocs29(q);
|
|
14966
|
+
snapshot.forEach((doc32) => {
|
|
14967
|
+
fetchedProcedures.push(doc32.data());
|
|
12473
14968
|
});
|
|
12474
14969
|
}
|
|
12475
14970
|
return fetchedProcedures;
|
|
@@ -12480,8 +14975,8 @@ var ProcedureService = class extends BaseService {
|
|
|
12480
14975
|
* @returns The procedure if found, null otherwise
|
|
12481
14976
|
*/
|
|
12482
14977
|
async getProcedure(id) {
|
|
12483
|
-
const docRef =
|
|
12484
|
-
const docSnap = await
|
|
14978
|
+
const docRef = doc30(this.db, PROCEDURES_COLLECTION, id);
|
|
14979
|
+
const docSnap = await getDoc32(docRef);
|
|
12485
14980
|
if (!docSnap.exists()) {
|
|
12486
14981
|
return null;
|
|
12487
14982
|
}
|
|
@@ -12493,13 +14988,13 @@ var ProcedureService = class extends BaseService {
|
|
|
12493
14988
|
* @returns List of procedures
|
|
12494
14989
|
*/
|
|
12495
14990
|
async getProceduresByClinicBranch(clinicBranchId) {
|
|
12496
|
-
const q =
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
14991
|
+
const q = query29(
|
|
14992
|
+
collection29(this.db, PROCEDURES_COLLECTION),
|
|
14993
|
+
where29("clinicBranchId", "==", clinicBranchId),
|
|
14994
|
+
where29("isActive", "==", true)
|
|
12500
14995
|
);
|
|
12501
|
-
const snapshot = await
|
|
12502
|
-
return snapshot.docs.map((
|
|
14996
|
+
const snapshot = await getDocs29(q);
|
|
14997
|
+
return snapshot.docs.map((doc32) => doc32.data());
|
|
12503
14998
|
}
|
|
12504
14999
|
/**
|
|
12505
15000
|
* Gets all procedures for a practitioner
|
|
@@ -12507,13 +15002,13 @@ var ProcedureService = class extends BaseService {
|
|
|
12507
15002
|
* @returns List of procedures
|
|
12508
15003
|
*/
|
|
12509
15004
|
async getProceduresByPractitioner(practitionerId) {
|
|
12510
|
-
const q =
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
15005
|
+
const q = query29(
|
|
15006
|
+
collection29(this.db, PROCEDURES_COLLECTION),
|
|
15007
|
+
where29("practitionerId", "==", practitionerId),
|
|
15008
|
+
where29("isActive", "==", true)
|
|
12514
15009
|
);
|
|
12515
|
-
const snapshot = await
|
|
12516
|
-
return snapshot.docs.map((
|
|
15010
|
+
const snapshot = await getDocs29(q);
|
|
15011
|
+
return snapshot.docs.map((doc32) => doc32.data());
|
|
12517
15012
|
}
|
|
12518
15013
|
/**
|
|
12519
15014
|
* Gets all inactive procedures for a practitioner
|
|
@@ -12521,13 +15016,13 @@ var ProcedureService = class extends BaseService {
|
|
|
12521
15016
|
* @returns List of inactive procedures
|
|
12522
15017
|
*/
|
|
12523
15018
|
async getInactiveProceduresByPractitioner(practitionerId) {
|
|
12524
|
-
const q =
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
15019
|
+
const q = query29(
|
|
15020
|
+
collection29(this.db, PROCEDURES_COLLECTION),
|
|
15021
|
+
where29("practitionerId", "==", practitionerId),
|
|
15022
|
+
where29("isActive", "==", false)
|
|
12528
15023
|
);
|
|
12529
|
-
const snapshot = await
|
|
12530
|
-
return snapshot.docs.map((
|
|
15024
|
+
const snapshot = await getDocs29(q);
|
|
15025
|
+
return snapshot.docs.map((doc32) => doc32.data());
|
|
12531
15026
|
}
|
|
12532
15027
|
/**
|
|
12533
15028
|
* Updates a procedure
|
|
@@ -12538,8 +15033,8 @@ var ProcedureService = class extends BaseService {
|
|
|
12538
15033
|
async updateProcedure(id, data) {
|
|
12539
15034
|
var _a;
|
|
12540
15035
|
const validatedData = updateProcedureSchema.parse(data);
|
|
12541
|
-
const procedureRef =
|
|
12542
|
-
const procedureSnapshot = await
|
|
15036
|
+
const procedureRef = doc30(this.db, PROCEDURES_COLLECTION, id);
|
|
15037
|
+
const procedureSnapshot = await getDoc32(procedureRef);
|
|
12543
15038
|
if (!procedureSnapshot.exists()) {
|
|
12544
15039
|
throw new Error(`Procedure with ID ${id} not found`);
|
|
12545
15040
|
}
|
|
@@ -12560,12 +15055,12 @@ var ProcedureService = class extends BaseService {
|
|
|
12560
15055
|
}
|
|
12561
15056
|
if (validatedData.practitionerId && validatedData.practitionerId !== oldPractitionerId) {
|
|
12562
15057
|
practitionerChanged = true;
|
|
12563
|
-
const newPractitionerRef =
|
|
15058
|
+
const newPractitionerRef = doc30(
|
|
12564
15059
|
this.db,
|
|
12565
15060
|
PRACTITIONERS_COLLECTION,
|
|
12566
15061
|
validatedData.practitionerId
|
|
12567
15062
|
);
|
|
12568
|
-
const newPractitionerSnap = await
|
|
15063
|
+
const newPractitionerSnap = await getDoc32(newPractitionerRef);
|
|
12569
15064
|
if (!newPractitionerSnap.exists())
|
|
12570
15065
|
throw new Error(
|
|
12571
15066
|
`New Practitioner ${validatedData.practitionerId} not found`
|
|
@@ -12583,12 +15078,12 @@ var ProcedureService = class extends BaseService {
|
|
|
12583
15078
|
}
|
|
12584
15079
|
if (validatedData.clinicBranchId && validatedData.clinicBranchId !== oldClinicId) {
|
|
12585
15080
|
clinicChanged = true;
|
|
12586
|
-
const newClinicRef =
|
|
15081
|
+
const newClinicRef = doc30(
|
|
12587
15082
|
this.db,
|
|
12588
15083
|
CLINICS_COLLECTION,
|
|
12589
15084
|
validatedData.clinicBranchId
|
|
12590
15085
|
);
|
|
12591
|
-
const newClinicSnap = await
|
|
15086
|
+
const newClinicSnap = await getDoc32(newClinicRef);
|
|
12592
15087
|
if (!newClinicSnap.exists())
|
|
12593
15088
|
throw new Error(`New Clinic ${validatedData.clinicBranchId} not found`);
|
|
12594
15089
|
newClinic = newClinicSnap.data();
|
|
@@ -12655,11 +15150,11 @@ var ProcedureService = class extends BaseService {
|
|
|
12655
15150
|
} else if (validatedData.productId) {
|
|
12656
15151
|
console.warn("Attempted to update product without a valid technologyId");
|
|
12657
15152
|
}
|
|
12658
|
-
await
|
|
15153
|
+
await updateDoc29(procedureRef, {
|
|
12659
15154
|
...updatedProcedureData,
|
|
12660
|
-
updatedAt:
|
|
15155
|
+
updatedAt: serverTimestamp25()
|
|
12661
15156
|
});
|
|
12662
|
-
const updatedSnapshot = await
|
|
15157
|
+
const updatedSnapshot = await getDoc32(procedureRef);
|
|
12663
15158
|
return updatedSnapshot.data();
|
|
12664
15159
|
}
|
|
12665
15160
|
/**
|
|
@@ -12667,15 +15162,15 @@ var ProcedureService = class extends BaseService {
|
|
|
12667
15162
|
* @param id - The ID of the procedure to deactivate
|
|
12668
15163
|
*/
|
|
12669
15164
|
async deactivateProcedure(id) {
|
|
12670
|
-
const procedureRef =
|
|
12671
|
-
const procedureSnap = await
|
|
15165
|
+
const procedureRef = doc30(this.db, PROCEDURES_COLLECTION, id);
|
|
15166
|
+
const procedureSnap = await getDoc32(procedureRef);
|
|
12672
15167
|
if (!procedureSnap.exists()) {
|
|
12673
15168
|
console.warn(`Procedure ${id} not found for deactivation.`);
|
|
12674
15169
|
return;
|
|
12675
15170
|
}
|
|
12676
|
-
await
|
|
15171
|
+
await updateDoc29(procedureRef, {
|
|
12677
15172
|
isActive: false,
|
|
12678
|
-
updatedAt:
|
|
15173
|
+
updatedAt: serverTimestamp25()
|
|
12679
15174
|
});
|
|
12680
15175
|
}
|
|
12681
15176
|
/**
|
|
@@ -12684,12 +15179,12 @@ var ProcedureService = class extends BaseService {
|
|
|
12684
15179
|
* @returns A boolean indicating if the deletion was successful
|
|
12685
15180
|
*/
|
|
12686
15181
|
async deleteProcedure(id) {
|
|
12687
|
-
const procedureRef =
|
|
12688
|
-
const procedureSnapshot = await
|
|
15182
|
+
const procedureRef = doc30(this.db, PROCEDURES_COLLECTION, id);
|
|
15183
|
+
const procedureSnapshot = await getDoc32(procedureRef);
|
|
12689
15184
|
if (!procedureSnapshot.exists()) {
|
|
12690
15185
|
return false;
|
|
12691
15186
|
}
|
|
12692
|
-
await
|
|
15187
|
+
await deleteDoc18(procedureRef);
|
|
12693
15188
|
return true;
|
|
12694
15189
|
}
|
|
12695
15190
|
/**
|
|
@@ -12715,35 +15210,35 @@ var ProcedureService = class extends BaseService {
|
|
|
12715
15210
|
*/
|
|
12716
15211
|
async getAllProcedures(pagination, lastDoc) {
|
|
12717
15212
|
try {
|
|
12718
|
-
const proceduresCollection =
|
|
12719
|
-
let proceduresQuery =
|
|
15213
|
+
const proceduresCollection = collection29(this.db, PROCEDURES_COLLECTION);
|
|
15214
|
+
let proceduresQuery = query29(proceduresCollection);
|
|
12720
15215
|
if (pagination && pagination > 0) {
|
|
12721
15216
|
const { limit: limit16, startAfter: startAfter14 } = await import("firebase/firestore");
|
|
12722
15217
|
if (lastDoc) {
|
|
12723
|
-
proceduresQuery =
|
|
15218
|
+
proceduresQuery = query29(
|
|
12724
15219
|
proceduresCollection,
|
|
12725
|
-
|
|
15220
|
+
orderBy17("name"),
|
|
12726
15221
|
// Use imported orderBy
|
|
12727
15222
|
startAfter14(lastDoc),
|
|
12728
15223
|
limit16(pagination)
|
|
12729
15224
|
);
|
|
12730
15225
|
} else {
|
|
12731
|
-
proceduresQuery =
|
|
15226
|
+
proceduresQuery = query29(
|
|
12732
15227
|
proceduresCollection,
|
|
12733
|
-
|
|
15228
|
+
orderBy17("name"),
|
|
12734
15229
|
limit16(pagination)
|
|
12735
15230
|
);
|
|
12736
15231
|
}
|
|
12737
15232
|
} else {
|
|
12738
|
-
proceduresQuery =
|
|
15233
|
+
proceduresQuery = query29(proceduresCollection, orderBy17("name"));
|
|
12739
15234
|
}
|
|
12740
|
-
const proceduresSnapshot = await
|
|
15235
|
+
const proceduresSnapshot = await getDocs29(proceduresQuery);
|
|
12741
15236
|
const lastVisible = proceduresSnapshot.docs[proceduresSnapshot.docs.length - 1];
|
|
12742
|
-
const procedures = proceduresSnapshot.docs.map((
|
|
12743
|
-
const data =
|
|
15237
|
+
const procedures = proceduresSnapshot.docs.map((doc32) => {
|
|
15238
|
+
const data = doc32.data();
|
|
12744
15239
|
return {
|
|
12745
15240
|
...data,
|
|
12746
|
-
id:
|
|
15241
|
+
id: doc32.id
|
|
12747
15242
|
// Ensure ID is present
|
|
12748
15243
|
};
|
|
12749
15244
|
});
|
|
@@ -12786,14 +15281,14 @@ var ProcedureService = class extends BaseService {
|
|
|
12786
15281
|
const isGeoQuery = filters.location && filters.radiusInKm && filters.radiusInKm > 0;
|
|
12787
15282
|
const constraints = [];
|
|
12788
15283
|
if (filters.isActive !== void 0) {
|
|
12789
|
-
constraints.push(
|
|
15284
|
+
constraints.push(where29("isActive", "==", filters.isActive));
|
|
12790
15285
|
} else {
|
|
12791
|
-
constraints.push(
|
|
15286
|
+
constraints.push(where29("isActive", "==", true));
|
|
12792
15287
|
}
|
|
12793
15288
|
if (filters.procedureFamily) {
|
|
12794
|
-
constraints.push(
|
|
15289
|
+
constraints.push(where29("family", "==", filters.procedureFamily));
|
|
12795
15290
|
}
|
|
12796
|
-
constraints.push(
|
|
15291
|
+
constraints.push(orderBy17("clinicInfo.location.geohash"));
|
|
12797
15292
|
if (filters.pagination && filters.pagination > 0 && filters.lastDoc) {
|
|
12798
15293
|
constraints.push(startAfter13(filters.lastDoc));
|
|
12799
15294
|
constraints.push(limit15(filters.pagination));
|
|
@@ -12814,19 +15309,19 @@ var ProcedureService = class extends BaseService {
|
|
|
12814
15309
|
for (const bound of bounds) {
|
|
12815
15310
|
const geoConstraints = [
|
|
12816
15311
|
...constraints,
|
|
12817
|
-
|
|
12818
|
-
|
|
15312
|
+
where29("clinicInfo.location.geohash", ">=", bound[0]),
|
|
15313
|
+
where29("clinicInfo.location.geohash", "<=", bound[1])
|
|
12819
15314
|
];
|
|
12820
|
-
const q =
|
|
12821
|
-
|
|
15315
|
+
const q = query29(
|
|
15316
|
+
collection29(this.db, PROCEDURES_COLLECTION),
|
|
12822
15317
|
...geoConstraints
|
|
12823
15318
|
);
|
|
12824
|
-
const querySnapshot = await
|
|
15319
|
+
const querySnapshot = await getDocs29(q);
|
|
12825
15320
|
console.log(
|
|
12826
15321
|
`[PROCEDURE_SERVICE] Found ${querySnapshot.docs.length} procedures in geo bound`
|
|
12827
15322
|
);
|
|
12828
|
-
for (const
|
|
12829
|
-
const procedure = { ...
|
|
15323
|
+
for (const doc32 of querySnapshot.docs) {
|
|
15324
|
+
const procedure = { ...doc32.data(), id: doc32.id };
|
|
12830
15325
|
const distance = distanceBetween6(
|
|
12831
15326
|
[center.latitude, center.longitude],
|
|
12832
15327
|
[
|
|
@@ -12869,16 +15364,16 @@ var ProcedureService = class extends BaseService {
|
|
|
12869
15364
|
proceduresResult = filteredProcedures;
|
|
12870
15365
|
}
|
|
12871
15366
|
} else {
|
|
12872
|
-
const q =
|
|
12873
|
-
|
|
15367
|
+
const q = query29(
|
|
15368
|
+
collection29(this.db, PROCEDURES_COLLECTION),
|
|
12874
15369
|
...constraints
|
|
12875
15370
|
);
|
|
12876
|
-
const querySnapshot = await
|
|
15371
|
+
const querySnapshot = await getDocs29(q);
|
|
12877
15372
|
console.log(
|
|
12878
15373
|
`[PROCEDURE_SERVICE] Found ${querySnapshot.docs.length} procedures with regular query`
|
|
12879
15374
|
);
|
|
12880
|
-
const procedures = querySnapshot.docs.map((
|
|
12881
|
-
return { ...
|
|
15375
|
+
const procedures = querySnapshot.docs.map((doc32) => {
|
|
15376
|
+
return { ...doc32.data(), id: doc32.id };
|
|
12882
15377
|
});
|
|
12883
15378
|
if (filters.location) {
|
|
12884
15379
|
const center = filters.location;
|
|
@@ -12999,18 +15494,18 @@ var ProcedureService = class extends BaseService {
|
|
|
12999
15494
|
if (!category || !subcategory || !technology) {
|
|
13000
15495
|
throw new Error("One or more required base entities not found");
|
|
13001
15496
|
}
|
|
13002
|
-
const clinicRef =
|
|
13003
|
-
const clinicSnapshot = await
|
|
15497
|
+
const clinicRef = doc30(this.db, CLINICS_COLLECTION, data.clinicBranchId);
|
|
15498
|
+
const clinicSnapshot = await getDoc32(clinicRef);
|
|
13004
15499
|
if (!clinicSnapshot.exists()) {
|
|
13005
15500
|
throw new Error(`Clinic with ID ${data.clinicBranchId} not found`);
|
|
13006
15501
|
}
|
|
13007
15502
|
const clinic = clinicSnapshot.data();
|
|
13008
|
-
const practitionerRef =
|
|
15503
|
+
const practitionerRef = doc30(
|
|
13009
15504
|
this.db,
|
|
13010
15505
|
PRACTITIONERS_COLLECTION,
|
|
13011
15506
|
data.practitionerId
|
|
13012
15507
|
);
|
|
13013
|
-
const practitionerSnapshot = await
|
|
15508
|
+
const practitionerSnapshot = await getDoc32(practitionerRef);
|
|
13014
15509
|
if (!practitionerSnapshot.exists()) {
|
|
13015
15510
|
throw new Error(`Practitioner with ID ${data.practitionerId} not found`);
|
|
13016
15511
|
}
|
|
@@ -13081,28 +15576,28 @@ var ProcedureService = class extends BaseService {
|
|
|
13081
15576
|
},
|
|
13082
15577
|
isActive: true
|
|
13083
15578
|
};
|
|
13084
|
-
const procedureRef =
|
|
13085
|
-
await
|
|
15579
|
+
const procedureRef = doc30(this.db, PROCEDURES_COLLECTION, procedureId);
|
|
15580
|
+
await setDoc26(procedureRef, {
|
|
13086
15581
|
...newProcedure,
|
|
13087
|
-
createdAt:
|
|
13088
|
-
updatedAt:
|
|
15582
|
+
createdAt: serverTimestamp25(),
|
|
15583
|
+
updatedAt: serverTimestamp25()
|
|
13089
15584
|
});
|
|
13090
|
-
const savedDoc = await
|
|
15585
|
+
const savedDoc = await getDoc32(procedureRef);
|
|
13091
15586
|
return savedDoc.data();
|
|
13092
15587
|
}
|
|
13093
15588
|
};
|
|
13094
15589
|
|
|
13095
15590
|
// src/services/reviews/reviews.service.ts
|
|
13096
15591
|
import {
|
|
13097
|
-
collection as
|
|
13098
|
-
doc as
|
|
13099
|
-
getDoc as
|
|
13100
|
-
getDocs as
|
|
13101
|
-
query as
|
|
13102
|
-
where as
|
|
13103
|
-
setDoc as
|
|
13104
|
-
deleteDoc as
|
|
13105
|
-
serverTimestamp as
|
|
15592
|
+
collection as collection30,
|
|
15593
|
+
doc as doc31,
|
|
15594
|
+
getDoc as getDoc33,
|
|
15595
|
+
getDocs as getDocs30,
|
|
15596
|
+
query as query30,
|
|
15597
|
+
where as where30,
|
|
15598
|
+
setDoc as setDoc27,
|
|
15599
|
+
deleteDoc as deleteDoc19,
|
|
15600
|
+
serverTimestamp as serverTimestamp26
|
|
13106
15601
|
} from "firebase/firestore";
|
|
13107
15602
|
import { z as z25 } from "zod";
|
|
13108
15603
|
var ReviewService = class extends BaseService {
|
|
@@ -13183,11 +15678,11 @@ var ReviewService = class extends BaseService {
|
|
|
13183
15678
|
updatedAt: now
|
|
13184
15679
|
};
|
|
13185
15680
|
reviewSchema.parse(review);
|
|
13186
|
-
const docRef =
|
|
13187
|
-
await
|
|
15681
|
+
const docRef = doc31(this.db, REVIEWS_COLLECTION, reviewId);
|
|
15682
|
+
await setDoc27(docRef, {
|
|
13188
15683
|
...review,
|
|
13189
|
-
createdAt:
|
|
13190
|
-
updatedAt:
|
|
15684
|
+
createdAt: serverTimestamp26(),
|
|
15685
|
+
updatedAt: serverTimestamp26()
|
|
13191
15686
|
});
|
|
13192
15687
|
return review;
|
|
13193
15688
|
} catch (error) {
|
|
@@ -13203,8 +15698,8 @@ var ReviewService = class extends BaseService {
|
|
|
13203
15698
|
* @returns The review if found, null otherwise
|
|
13204
15699
|
*/
|
|
13205
15700
|
async getReview(reviewId) {
|
|
13206
|
-
const docRef =
|
|
13207
|
-
const docSnap = await
|
|
15701
|
+
const docRef = doc31(this.db, REVIEWS_COLLECTION, reviewId);
|
|
15702
|
+
const docSnap = await getDoc33(docRef);
|
|
13208
15703
|
if (!docSnap.exists()) {
|
|
13209
15704
|
return null;
|
|
13210
15705
|
}
|
|
@@ -13216,12 +15711,12 @@ var ReviewService = class extends BaseService {
|
|
|
13216
15711
|
* @returns Array of reviews for the patient
|
|
13217
15712
|
*/
|
|
13218
15713
|
async getReviewsByPatient(patientId) {
|
|
13219
|
-
const q =
|
|
13220
|
-
|
|
13221
|
-
|
|
15714
|
+
const q = query30(
|
|
15715
|
+
collection30(this.db, REVIEWS_COLLECTION),
|
|
15716
|
+
where30("patientId", "==", patientId)
|
|
13222
15717
|
);
|
|
13223
|
-
const snapshot = await
|
|
13224
|
-
return snapshot.docs.map((
|
|
15718
|
+
const snapshot = await getDocs30(q);
|
|
15719
|
+
return snapshot.docs.map((doc32) => doc32.data());
|
|
13225
15720
|
}
|
|
13226
15721
|
/**
|
|
13227
15722
|
* Gets all reviews for a specific clinic
|
|
@@ -13229,12 +15724,12 @@ var ReviewService = class extends BaseService {
|
|
|
13229
15724
|
* @returns Array of reviews containing clinic reviews
|
|
13230
15725
|
*/
|
|
13231
15726
|
async getReviewsByClinic(clinicId) {
|
|
13232
|
-
const q =
|
|
13233
|
-
|
|
13234
|
-
|
|
15727
|
+
const q = query30(
|
|
15728
|
+
collection30(this.db, REVIEWS_COLLECTION),
|
|
15729
|
+
where30("clinicReview.clinicId", "==", clinicId)
|
|
13235
15730
|
);
|
|
13236
|
-
const snapshot = await
|
|
13237
|
-
return snapshot.docs.map((
|
|
15731
|
+
const snapshot = await getDocs30(q);
|
|
15732
|
+
return snapshot.docs.map((doc32) => doc32.data());
|
|
13238
15733
|
}
|
|
13239
15734
|
/**
|
|
13240
15735
|
* Gets all reviews for a specific practitioner
|
|
@@ -13242,12 +15737,12 @@ var ReviewService = class extends BaseService {
|
|
|
13242
15737
|
* @returns Array of reviews containing practitioner reviews
|
|
13243
15738
|
*/
|
|
13244
15739
|
async getReviewsByPractitioner(practitionerId) {
|
|
13245
|
-
const q =
|
|
13246
|
-
|
|
13247
|
-
|
|
15740
|
+
const q = query30(
|
|
15741
|
+
collection30(this.db, REVIEWS_COLLECTION),
|
|
15742
|
+
where30("practitionerReview.practitionerId", "==", practitionerId)
|
|
13248
15743
|
);
|
|
13249
|
-
const snapshot = await
|
|
13250
|
-
return snapshot.docs.map((
|
|
15744
|
+
const snapshot = await getDocs30(q);
|
|
15745
|
+
return snapshot.docs.map((doc32) => doc32.data());
|
|
13251
15746
|
}
|
|
13252
15747
|
/**
|
|
13253
15748
|
* Gets all reviews for a specific procedure
|
|
@@ -13255,12 +15750,12 @@ var ReviewService = class extends BaseService {
|
|
|
13255
15750
|
* @returns Array of reviews containing procedure reviews
|
|
13256
15751
|
*/
|
|
13257
15752
|
async getReviewsByProcedure(procedureId) {
|
|
13258
|
-
const q =
|
|
13259
|
-
|
|
13260
|
-
|
|
15753
|
+
const q = query30(
|
|
15754
|
+
collection30(this.db, REVIEWS_COLLECTION),
|
|
15755
|
+
where30("procedureReview.procedureId", "==", procedureId)
|
|
13261
15756
|
);
|
|
13262
|
-
const snapshot = await
|
|
13263
|
-
return snapshot.docs.map((
|
|
15757
|
+
const snapshot = await getDocs30(q);
|
|
15758
|
+
return snapshot.docs.map((doc32) => doc32.data());
|
|
13264
15759
|
}
|
|
13265
15760
|
/**
|
|
13266
15761
|
* Gets all reviews for a specific appointment
|
|
@@ -13268,11 +15763,11 @@ var ReviewService = class extends BaseService {
|
|
|
13268
15763
|
* @returns The review for the appointment if found, null otherwise
|
|
13269
15764
|
*/
|
|
13270
15765
|
async getReviewByAppointment(appointmentId) {
|
|
13271
|
-
const q =
|
|
13272
|
-
|
|
13273
|
-
|
|
15766
|
+
const q = query30(
|
|
15767
|
+
collection30(this.db, REVIEWS_COLLECTION),
|
|
15768
|
+
where30("appointmentId", "==", appointmentId)
|
|
13274
15769
|
);
|
|
13275
|
-
const snapshot = await
|
|
15770
|
+
const snapshot = await getDocs30(q);
|
|
13276
15771
|
if (snapshot.empty) {
|
|
13277
15772
|
return null;
|
|
13278
15773
|
}
|
|
@@ -13287,7 +15782,7 @@ var ReviewService = class extends BaseService {
|
|
|
13287
15782
|
if (!review) {
|
|
13288
15783
|
throw new Error(`Review with ID ${reviewId} not found`);
|
|
13289
15784
|
}
|
|
13290
|
-
await
|
|
15785
|
+
await deleteDoc19(doc31(this.db, REVIEWS_COLLECTION, reviewId));
|
|
13291
15786
|
}
|
|
13292
15787
|
/**
|
|
13293
15788
|
* Calculates the average of an array of numbers
|
|
@@ -13306,11 +15801,11 @@ var ReviewService = class extends BaseService {
|
|
|
13306
15801
|
|
|
13307
15802
|
// src/validations/calendar.schema.ts
|
|
13308
15803
|
import { z as z27 } from "zod";
|
|
13309
|
-
import { Timestamp as
|
|
15804
|
+
import { Timestamp as Timestamp34 } from "firebase/firestore";
|
|
13310
15805
|
|
|
13311
15806
|
// src/validations/profile-info.schema.ts
|
|
13312
15807
|
import { z as z26 } from "zod";
|
|
13313
|
-
import { Timestamp as
|
|
15808
|
+
import { Timestamp as Timestamp33 } from "firebase/firestore";
|
|
13314
15809
|
var clinicBranchInfoSchema = z26.object({
|
|
13315
15810
|
id: z26.string(),
|
|
13316
15811
|
featuredPhoto: z26.string(),
|
|
@@ -13332,19 +15827,19 @@ var patientProfileInfoSchema = z26.object({
|
|
|
13332
15827
|
fullName: z26.string(),
|
|
13333
15828
|
email: z26.string().email(),
|
|
13334
15829
|
phone: z26.string().nullable(),
|
|
13335
|
-
dateOfBirth: z26.instanceof(
|
|
15830
|
+
dateOfBirth: z26.instanceof(Timestamp33),
|
|
13336
15831
|
gender: z26.nativeEnum(Gender)
|
|
13337
15832
|
});
|
|
13338
15833
|
|
|
13339
15834
|
// src/validations/calendar.schema.ts
|
|
13340
|
-
var
|
|
15835
|
+
var MIN_APPOINTMENT_DURATION2 = 15;
|
|
13341
15836
|
var calendarEventTimeSchema = z27.object({
|
|
13342
|
-
start: z27.instanceof(Date).or(z27.instanceof(
|
|
13343
|
-
end: z27.instanceof(Date).or(z27.instanceof(
|
|
15837
|
+
start: z27.instanceof(Date).or(z27.instanceof(Timestamp34)),
|
|
15838
|
+
end: z27.instanceof(Date).or(z27.instanceof(Timestamp34))
|
|
13344
15839
|
}).refine(
|
|
13345
15840
|
(data) => {
|
|
13346
|
-
const startDate = data.start instanceof
|
|
13347
|
-
const endDate = data.end instanceof
|
|
15841
|
+
const startDate = data.start instanceof Timestamp34 ? data.start.toDate() : data.start;
|
|
15842
|
+
const endDate = data.end instanceof Timestamp34 ? data.end.toDate() : data.end;
|
|
13348
15843
|
return startDate < endDate;
|
|
13349
15844
|
},
|
|
13350
15845
|
{
|
|
@@ -13353,7 +15848,7 @@ var calendarEventTimeSchema = z27.object({
|
|
|
13353
15848
|
}
|
|
13354
15849
|
).refine(
|
|
13355
15850
|
(data) => {
|
|
13356
|
-
const startDate = data.start instanceof
|
|
15851
|
+
const startDate = data.start instanceof Timestamp34 ? data.start.toDate() : data.start;
|
|
13357
15852
|
return startDate > /* @__PURE__ */ new Date();
|
|
13358
15853
|
},
|
|
13359
15854
|
{
|
|
@@ -13372,12 +15867,12 @@ var timeSlotSchema2 = z27.object({
|
|
|
13372
15867
|
var syncedCalendarEventSchema = z27.object({
|
|
13373
15868
|
eventId: z27.string(),
|
|
13374
15869
|
syncedCalendarProvider: z27.nativeEnum(SyncedCalendarProvider),
|
|
13375
|
-
syncedAt: z27.instanceof(Date).or(z27.instanceof(
|
|
15870
|
+
syncedAt: z27.instanceof(Date).or(z27.instanceof(Timestamp34))
|
|
13376
15871
|
});
|
|
13377
15872
|
var procedureInfoSchema = z27.object({
|
|
13378
15873
|
name: z27.string(),
|
|
13379
15874
|
description: z27.string(),
|
|
13380
|
-
duration: z27.number().min(
|
|
15875
|
+
duration: z27.number().min(MIN_APPOINTMENT_DURATION2),
|
|
13381
15876
|
price: z27.number().min(0),
|
|
13382
15877
|
currency: z27.nativeEnum(Currency)
|
|
13383
15878
|
});
|
|
@@ -13451,8 +15946,8 @@ var calendarEventSchema = z27.object({
|
|
|
13451
15946
|
status: z27.nativeEnum(CalendarEventStatus),
|
|
13452
15947
|
syncStatus: z27.nativeEnum(CalendarSyncStatus),
|
|
13453
15948
|
eventType: z27.nativeEnum(CalendarEventType),
|
|
13454
|
-
createdAt: z27.instanceof(Date).or(z27.instanceof(
|
|
13455
|
-
updatedAt: z27.instanceof(Date).or(z27.instanceof(
|
|
15949
|
+
createdAt: z27.instanceof(Date).or(z27.instanceof(Timestamp34)),
|
|
15950
|
+
updatedAt: z27.instanceof(Date).or(z27.instanceof(Timestamp34))
|
|
13456
15951
|
});
|
|
13457
15952
|
var createBlockingEventSchema = z27.object({
|
|
13458
15953
|
entityType: z27.enum(["practitioner", "clinic"]),
|
|
@@ -13580,11 +16075,11 @@ var RequirementType = /* @__PURE__ */ ((RequirementType2) => {
|
|
|
13580
16075
|
})(RequirementType || {});
|
|
13581
16076
|
|
|
13582
16077
|
// src/backoffice/validations/schemas.ts
|
|
13583
|
-
var
|
|
13584
|
-
var
|
|
13585
|
-
var
|
|
13586
|
-
var
|
|
13587
|
-
var
|
|
16078
|
+
var blockingConditionSchemaBackoffice = z29.nativeEnum(BlockingCondition);
|
|
16079
|
+
var contraindicationSchemaBackoffice = z29.nativeEnum(Contraindication);
|
|
16080
|
+
var treatmentBenefitSchemaBackoffice = z29.nativeEnum(TreatmentBenefit);
|
|
16081
|
+
var procedureFamilySchemaBackoffice = z29.nativeEnum(ProcedureFamily);
|
|
16082
|
+
var timeUnitSchemaBackoffice = z29.nativeEnum(TimeUnit);
|
|
13588
16083
|
var requirementTypeSchema = z29.nativeEnum(RequirementType);
|
|
13589
16084
|
var certificationLevelSchema = z29.nativeEnum(CertificationLevel);
|
|
13590
16085
|
var certificationSpecialtySchema = z29.nativeEnum(
|
|
@@ -13596,7 +16091,7 @@ var certificationRequirementSchema = z29.object({
|
|
|
13596
16091
|
});
|
|
13597
16092
|
var timeframeSchema = z29.object({
|
|
13598
16093
|
duration: z29.number().min(1, "Duration must be positive"),
|
|
13599
|
-
unit:
|
|
16094
|
+
unit: timeUnitSchemaBackoffice,
|
|
13600
16095
|
notifyAt: z29.array(z29.number()).min(1, "At least one notification point is required")
|
|
13601
16096
|
});
|
|
13602
16097
|
var requirementSchema = z29.object({
|
|
@@ -13615,24 +16110,24 @@ var technologySchema = z29.object({
|
|
|
13615
16110
|
name: z29.string().min(1, "Name is required").max(100, "Name is too long"),
|
|
13616
16111
|
description: z29.string().max(1e3, "Description is too long").optional(),
|
|
13617
16112
|
technicalDetails: z29.string().max(2e3, "Technical details are too long").optional(),
|
|
13618
|
-
family:
|
|
16113
|
+
family: procedureFamilySchemaBackoffice,
|
|
13619
16114
|
categoryId: z29.string().min(1, "Category ID is required"),
|
|
13620
16115
|
subcategoryId: z29.string().min(1, "Subcategory ID is required"),
|
|
13621
16116
|
requirements: technologyRequirementsSchema.default({
|
|
13622
16117
|
pre: [],
|
|
13623
16118
|
post: []
|
|
13624
16119
|
}),
|
|
13625
|
-
blockingConditions: z29.array(
|
|
13626
|
-
contraindications: z29.array(
|
|
16120
|
+
blockingConditions: z29.array(blockingConditionSchemaBackoffice),
|
|
16121
|
+
contraindications: z29.array(contraindicationSchemaBackoffice),
|
|
13627
16122
|
documentationTemplates: z29.array(documentTemplateSchema),
|
|
13628
|
-
benefits: z29.array(
|
|
16123
|
+
benefits: z29.array(treatmentBenefitSchemaBackoffice),
|
|
13629
16124
|
certificationRequirement: certificationRequirementSchema,
|
|
13630
16125
|
isActive: z29.boolean().default(true)
|
|
13631
16126
|
});
|
|
13632
16127
|
var categorySchema = z29.object({
|
|
13633
16128
|
name: z29.string().min(1, "Name is required").max(100, "Name is too long"),
|
|
13634
16129
|
description: z29.string().optional(),
|
|
13635
|
-
family:
|
|
16130
|
+
family: procedureFamilySchemaBackoffice,
|
|
13636
16131
|
isActive: z29.boolean().default(true)
|
|
13637
16132
|
});
|
|
13638
16133
|
var subcategorySchema = z29.object({
|
|
@@ -13658,7 +16153,7 @@ var patientRequirementInstructionSchema = z30.object({
|
|
|
13658
16153
|
actionableWindow: z30.number().default(1),
|
|
13659
16154
|
status: patientInstructionStatusSchema,
|
|
13660
16155
|
originalNotifyAtValue: z30.number(),
|
|
13661
|
-
originalTimeframeUnit:
|
|
16156
|
+
originalTimeframeUnit: timeUnitSchemaBackoffice,
|
|
13662
16157
|
// Use the correctly imported timeUnitSchema
|
|
13663
16158
|
notificationId: z30.string().optional(),
|
|
13664
16159
|
actionTakenAt: z30.any().optional().nullable(),
|
|
@@ -13940,6 +16435,7 @@ export {
|
|
|
13940
16435
|
CLINIC_GROUPS_COLLECTION,
|
|
13941
16436
|
CalendarEventStatus,
|
|
13942
16437
|
CalendarEventType,
|
|
16438
|
+
CalendarServiceV2,
|
|
13943
16439
|
CalendarServiceV3,
|
|
13944
16440
|
CalendarSyncStatus,
|
|
13945
16441
|
ClinicAdminService,
|
|
@@ -14008,6 +16504,7 @@ export {
|
|
|
14008
16504
|
USERS_COLLECTION,
|
|
14009
16505
|
USER_FORMS_SUBCOLLECTION,
|
|
14010
16506
|
UserRole,
|
|
16507
|
+
UserService,
|
|
14011
16508
|
addAllergySchema,
|
|
14012
16509
|
addBlockingConditionSchema,
|
|
14013
16510
|
addContraindicationSchema,
|