@blackcode_sa/metaestetics-api 1.12.61 → 1.12.62

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.
Files changed (269) hide show
  1. package/dist/admin/index.d.mts +2 -0
  2. package/dist/admin/index.d.ts +2 -0
  3. package/dist/admin/index.js +45 -4
  4. package/dist/admin/index.mjs +45 -4
  5. package/dist/index.d.mts +2 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +53 -11
  8. package/dist/index.mjs +53 -11
  9. package/package.json +119 -119
  10. package/src/__mocks__/firstore.ts +10 -10
  11. package/src/admin/aggregation/README.md +79 -79
  12. package/src/admin/aggregation/appointment/README.md +128 -128
  13. package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +1844 -1844
  14. package/src/admin/aggregation/appointment/index.ts +1 -1
  15. package/src/admin/aggregation/clinic/README.md +52 -52
  16. package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +703 -703
  17. package/src/admin/aggregation/clinic/index.ts +1 -1
  18. package/src/admin/aggregation/forms/README.md +13 -13
  19. package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
  20. package/src/admin/aggregation/forms/index.ts +1 -1
  21. package/src/admin/aggregation/index.ts +8 -8
  22. package/src/admin/aggregation/patient/README.md +27 -27
  23. package/src/admin/aggregation/patient/index.ts +1 -1
  24. package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
  25. package/src/admin/aggregation/practitioner/README.md +42 -42
  26. package/src/admin/aggregation/practitioner/index.ts +1 -1
  27. package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
  28. package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
  29. package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
  30. package/src/admin/aggregation/procedure/README.md +43 -43
  31. package/src/admin/aggregation/procedure/index.ts +1 -1
  32. package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
  33. package/src/admin/aggregation/reviews/index.ts +1 -1
  34. package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +689 -641
  35. package/src/admin/booking/README.md +125 -125
  36. package/src/admin/booking/booking.admin.ts +1037 -1037
  37. package/src/admin/booking/booking.calculator.ts +712 -712
  38. package/src/admin/booking/booking.types.ts +59 -59
  39. package/src/admin/booking/index.ts +3 -3
  40. package/src/admin/booking/timezones-problem.md +185 -185
  41. package/src/admin/calendar/README.md +7 -7
  42. package/src/admin/calendar/calendar.admin.service.ts +345 -345
  43. package/src/admin/calendar/index.ts +1 -1
  44. package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
  45. package/src/admin/documentation-templates/index.ts +1 -1
  46. package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
  47. package/src/admin/free-consultation/index.ts +1 -1
  48. package/src/admin/index.ts +75 -75
  49. package/src/admin/logger/index.ts +78 -78
  50. package/src/admin/mailing/README.md +95 -95
  51. package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
  52. package/src/admin/mailing/appointment/index.ts +1 -1
  53. package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
  54. package/src/admin/mailing/base.mailing.service.ts +208 -208
  55. package/src/admin/mailing/index.ts +3 -3
  56. package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
  57. package/src/admin/mailing/practitionerInvite/index.ts +2 -2
  58. package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
  59. package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
  60. package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
  61. package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
  62. package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
  63. package/src/admin/notifications/index.ts +1 -1
  64. package/src/admin/notifications/notifications.admin.ts +710 -710
  65. package/src/admin/requirements/README.md +128 -128
  66. package/src/admin/requirements/index.ts +1 -1
  67. package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
  68. package/src/admin/users/index.ts +1 -1
  69. package/src/admin/users/user-profile.admin.ts +405 -405
  70. package/src/backoffice/constants/certification.constants.ts +13 -13
  71. package/src/backoffice/constants/index.ts +1 -1
  72. package/src/backoffice/errors/backoffice.errors.ts +181 -181
  73. package/src/backoffice/errors/index.ts +1 -1
  74. package/src/backoffice/expo-safe/README.md +26 -26
  75. package/src/backoffice/expo-safe/index.ts +41 -41
  76. package/src/backoffice/index.ts +5 -5
  77. package/src/backoffice/services/FIXES_README.md +102 -102
  78. package/src/backoffice/services/README.md +40 -40
  79. package/src/backoffice/services/brand.service.ts +256 -256
  80. package/src/backoffice/services/category.service.ts +318 -318
  81. package/src/backoffice/services/constants.service.ts +385 -385
  82. package/src/backoffice/services/documentation-template.service.ts +202 -202
  83. package/src/backoffice/services/index.ts +8 -8
  84. package/src/backoffice/services/migrate-products.ts +116 -116
  85. package/src/backoffice/services/product.service.ts +553 -553
  86. package/src/backoffice/services/requirement.service.ts +235 -235
  87. package/src/backoffice/services/subcategory.service.ts +395 -395
  88. package/src/backoffice/services/technology.service.ts +1070 -1070
  89. package/src/backoffice/types/README.md +12 -12
  90. package/src/backoffice/types/admin-constants.types.ts +69 -69
  91. package/src/backoffice/types/brand.types.ts +29 -29
  92. package/src/backoffice/types/category.types.ts +62 -62
  93. package/src/backoffice/types/documentation-templates.types.ts +28 -28
  94. package/src/backoffice/types/index.ts +10 -10
  95. package/src/backoffice/types/procedure-product.types.ts +38 -38
  96. package/src/backoffice/types/product.types.ts +240 -240
  97. package/src/backoffice/types/requirement.types.ts +63 -63
  98. package/src/backoffice/types/static/README.md +18 -18
  99. package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
  100. package/src/backoffice/types/static/certification.types.ts +37 -37
  101. package/src/backoffice/types/static/contraindication.types.ts +19 -19
  102. package/src/backoffice/types/static/index.ts +6 -6
  103. package/src/backoffice/types/static/pricing.types.ts +16 -16
  104. package/src/backoffice/types/static/procedure-family.types.ts +14 -14
  105. package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
  106. package/src/backoffice/types/subcategory.types.ts +34 -34
  107. package/src/backoffice/types/technology.types.ts +161 -161
  108. package/src/backoffice/validations/index.ts +1 -1
  109. package/src/backoffice/validations/schemas.ts +163 -163
  110. package/src/config/__mocks__/firebase.ts +99 -99
  111. package/src/config/firebase.ts +78 -78
  112. package/src/config/index.ts +9 -9
  113. package/src/errors/auth.error.ts +6 -6
  114. package/src/errors/auth.errors.ts +200 -200
  115. package/src/errors/clinic.errors.ts +32 -32
  116. package/src/errors/firebase.errors.ts +47 -47
  117. package/src/errors/user.errors.ts +99 -99
  118. package/src/index.backup.ts +407 -407
  119. package/src/index.ts +6 -6
  120. package/src/locales/en.ts +31 -31
  121. package/src/recommender/admin/index.ts +1 -1
  122. package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
  123. package/src/recommender/front/index.ts +1 -1
  124. package/src/recommender/front/services/onboarding.service.ts +5 -5
  125. package/src/recommender/front/services/recommender.service.ts +3 -3
  126. package/src/recommender/index.ts +1 -1
  127. package/src/services/PATIENTAUTH.MD +197 -197
  128. package/src/services/README.md +106 -106
  129. package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
  130. package/src/services/__tests__/auth/auth.setup.ts +293 -293
  131. package/src/services/__tests__/auth.service.test.ts +346 -346
  132. package/src/services/__tests__/base.service.test.ts +77 -77
  133. package/src/services/__tests__/user.service.test.ts +528 -528
  134. package/src/services/appointment/README.md +17 -17
  135. package/src/services/appointment/appointment.service.ts +2082 -2082
  136. package/src/services/appointment/index.ts +1 -1
  137. package/src/services/appointment/utils/appointment.utils.ts +552 -552
  138. package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
  139. package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
  140. package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
  141. package/src/services/appointment/utils/zone-management.utils.ts +353 -353
  142. package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
  143. package/src/services/auth/auth.service.ts +989 -989
  144. package/src/services/auth/auth.v2.service.ts +961 -961
  145. package/src/services/auth/index.ts +7 -7
  146. package/src/services/auth/utils/error.utils.ts +90 -90
  147. package/src/services/auth/utils/firebase.utils.ts +49 -49
  148. package/src/services/auth/utils/index.ts +21 -21
  149. package/src/services/auth/utils/practitioner.utils.ts +125 -125
  150. package/src/services/base.service.ts +41 -41
  151. package/src/services/calendar/calendar.service.ts +1077 -1077
  152. package/src/services/calendar/calendar.v2.service.ts +1683 -1683
  153. package/src/services/calendar/calendar.v3.service.ts +313 -313
  154. package/src/services/calendar/externalCalendar.service.ts +178 -178
  155. package/src/services/calendar/index.ts +5 -5
  156. package/src/services/calendar/synced-calendars.service.ts +743 -743
  157. package/src/services/calendar/utils/appointment.utils.ts +265 -265
  158. package/src/services/calendar/utils/calendar-event.utils.ts +646 -646
  159. package/src/services/calendar/utils/clinic.utils.ts +237 -237
  160. package/src/services/calendar/utils/docs.utils.ts +157 -157
  161. package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
  162. package/src/services/calendar/utils/index.ts +8 -8
  163. package/src/services/calendar/utils/patient.utils.ts +198 -198
  164. package/src/services/calendar/utils/practitioner.utils.ts +221 -221
  165. package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
  166. package/src/services/clinic/README.md +204 -204
  167. package/src/services/clinic/__tests__/clinic-admin.service.test.ts +287 -287
  168. package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
  169. package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
  170. package/src/services/clinic/billing-transactions.service.ts +217 -217
  171. package/src/services/clinic/clinic-admin.service.ts +202 -202
  172. package/src/services/clinic/clinic-group.service.ts +310 -310
  173. package/src/services/clinic/clinic.service.ts +708 -708
  174. package/src/services/clinic/index.ts +5 -5
  175. package/src/services/clinic/practitioner-invite.service.ts +519 -519
  176. package/src/services/clinic/utils/admin.utils.ts +551 -551
  177. package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
  178. package/src/services/clinic/utils/clinic.utils.ts +949 -949
  179. package/src/services/clinic/utils/filter.utils.d.ts +23 -23
  180. package/src/services/clinic/utils/filter.utils.ts +446 -446
  181. package/src/services/clinic/utils/index.ts +11 -11
  182. package/src/services/clinic/utils/photos.utils.ts +188 -188
  183. package/src/services/clinic/utils/search.utils.ts +84 -84
  184. package/src/services/clinic/utils/tag.utils.ts +124 -124
  185. package/src/services/documentation-templates/documentation-template.service.ts +537 -537
  186. package/src/services/documentation-templates/filled-document.service.ts +587 -587
  187. package/src/services/documentation-templates/index.ts +2 -2
  188. package/src/services/index.ts +13 -13
  189. package/src/services/media/index.ts +1 -1
  190. package/src/services/media/media.service.ts +418 -418
  191. package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
  192. package/src/services/notifications/index.ts +1 -1
  193. package/src/services/notifications/notification.service.ts +215 -215
  194. package/src/services/patient/README.md +48 -48
  195. package/src/services/patient/To-Do.md +43 -43
  196. package/src/services/patient/__tests__/patient.service.test.ts +294 -294
  197. package/src/services/patient/index.ts +2 -2
  198. package/src/services/patient/patient.service.ts +883 -883
  199. package/src/services/patient/patientRequirements.service.ts +285 -285
  200. package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
  201. package/src/services/patient/utils/clinic.utils.ts +80 -80
  202. package/src/services/patient/utils/docs.utils.ts +142 -142
  203. package/src/services/patient/utils/index.ts +9 -9
  204. package/src/services/patient/utils/location.utils.ts +126 -126
  205. package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
  206. package/src/services/patient/utils/medical.utils.ts +458 -458
  207. package/src/services/patient/utils/practitioner.utils.ts +260 -260
  208. package/src/services/patient/utils/profile.utils.ts +510 -510
  209. package/src/services/patient/utils/sensitive.utils.ts +260 -260
  210. package/src/services/patient/utils/token.utils.ts +211 -211
  211. package/src/services/practitioner/README.md +145 -145
  212. package/src/services/practitioner/index.ts +1 -1
  213. package/src/services/practitioner/practitioner.service.ts +1742 -1742
  214. package/src/services/procedure/README.md +163 -163
  215. package/src/services/procedure/index.ts +1 -1
  216. package/src/services/procedure/procedure.service.ts +1682 -1682
  217. package/src/services/reviews/index.ts +1 -1
  218. package/src/services/reviews/reviews.service.ts +683 -636
  219. package/src/services/user/index.ts +1 -1
  220. package/src/services/user/user.service.ts +489 -489
  221. package/src/services/user/user.v2.service.ts +466 -466
  222. package/src/types/appointment/index.ts +453 -453
  223. package/src/types/calendar/index.ts +258 -258
  224. package/src/types/calendar/synced-calendar.types.ts +66 -66
  225. package/src/types/clinic/index.ts +489 -489
  226. package/src/types/clinic/practitioner-invite.types.ts +91 -91
  227. package/src/types/clinic/preferences.types.ts +159 -159
  228. package/src/types/clinic/to-do +3 -3
  229. package/src/types/documentation-templates/index.ts +308 -308
  230. package/src/types/index.ts +44 -44
  231. package/src/types/notifications/README.md +77 -77
  232. package/src/types/notifications/index.ts +265 -265
  233. package/src/types/patient/aesthetic-analysis.types.ts +66 -66
  234. package/src/types/patient/allergies.ts +58 -58
  235. package/src/types/patient/index.ts +273 -273
  236. package/src/types/patient/medical-info.types.ts +152 -152
  237. package/src/types/patient/patient-requirements.ts +92 -92
  238. package/src/types/patient/token.types.ts +61 -61
  239. package/src/types/practitioner/index.ts +206 -206
  240. package/src/types/procedure/index.ts +181 -181
  241. package/src/types/profile/index.ts +39 -39
  242. package/src/types/reviews/index.ts +132 -130
  243. package/src/types/tz-lookup.d.ts +4 -4
  244. package/src/types/user/index.ts +38 -38
  245. package/src/utils/TIMESTAMPS.md +176 -176
  246. package/src/utils/TimestampUtils.ts +241 -241
  247. package/src/utils/index.ts +1 -1
  248. package/src/validations/appointment.schema.ts +574 -574
  249. package/src/validations/calendar.schema.ts +225 -225
  250. package/src/validations/clinic.schema.ts +493 -493
  251. package/src/validations/common.schema.ts +25 -25
  252. package/src/validations/documentation-templates/index.ts +1 -1
  253. package/src/validations/documentation-templates/template.schema.ts +220 -220
  254. package/src/validations/documentation-templates.schema.ts +10 -10
  255. package/src/validations/index.ts +20 -20
  256. package/src/validations/media.schema.ts +10 -10
  257. package/src/validations/notification.schema.ts +90 -90
  258. package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
  259. package/src/validations/patient/medical-info.schema.ts +125 -125
  260. package/src/validations/patient/patient-requirements.schema.ts +84 -84
  261. package/src/validations/patient/token.schema.ts +29 -29
  262. package/src/validations/patient.schema.ts +216 -216
  263. package/src/validations/practitioner.schema.ts +222 -222
  264. package/src/validations/procedure-product.schema.ts +41 -41
  265. package/src/validations/procedure.schema.ts +124 -124
  266. package/src/validations/profile-info.schema.ts +41 -41
  267. package/src/validations/reviews.schema.ts +195 -189
  268. package/src/validations/schemas.ts +104 -104
  269. package/src/validations/shared.schema.ts +78 -78
@@ -1,1077 +1,1077 @@
1
- import { Auth } from "firebase/auth";
2
- import { Firestore, Timestamp } from "firebase/firestore";
3
- import { FirebaseApp } from "firebase/app";
4
- import { BaseService } from "../base.service";
5
- import {
6
- CalendarEvent,
7
- CalendarEventStatus,
8
- CalendarEventTime,
9
- CalendarEventType,
10
- CalendarSyncStatus,
11
- CreateCalendarEventData,
12
- UpdateCalendarEventData,
13
- CALENDAR_COLLECTION,
14
- SyncedCalendarEvent,
15
- } from "../../types/calendar";
16
- import { PRACTITIONERS_COLLECTION } from "../../types/practitioner";
17
- import { PATIENTS_COLLECTION } from "../../types/patient";
18
- import { CLINICS_COLLECTION } from "../../types/clinic";
19
- import { SyncedCalendarProvider } from "../../types/calendar/synced-calendar.types";
20
-
21
- // Import utility functions from the index file
22
- import {
23
- createPractitionerCalendarEventUtil,
24
- createPatientCalendarEventUtil,
25
- createClinicCalendarEventUtil,
26
- getPractitionerCalendarEventUtil,
27
- getPatientCalendarEventUtil,
28
- getClinicCalendarEventUtil,
29
- getPractitionerCalendarEventsUtil,
30
- getPatientCalendarEventsUtil,
31
- getClinicCalendarEventsUtil,
32
- updatePractitionerCalendarEventUtil,
33
- updatePatientCalendarEventUtil,
34
- updateClinicCalendarEventUtil,
35
- deletePractitionerCalendarEventUtil,
36
- deletePatientCalendarEventUtil,
37
- deleteClinicCalendarEventUtil,
38
- } from "./utils";
39
-
40
- // Import the SyncedCalendarsService for calendar synchronization
41
- import { SyncedCalendarsService } from "./synced-calendars.service";
42
-
43
- /**
44
- * Service for managing calendar events
45
- */
46
- export class CalendarService extends BaseService {
47
- private syncedCalendarsService: SyncedCalendarsService;
48
-
49
- /**
50
- * Creates a new CalendarService instance
51
- * @param db - Firestore instance
52
- * @param auth - Firebase Auth instance
53
- * @param app - Firebase App instance
54
- */
55
- constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
56
- super(db, auth, app);
57
- this.syncedCalendarsService = new SyncedCalendarsService(db, auth, app);
58
- }
59
-
60
- /**
61
- * Creates a calendar event for a practitioner
62
- * @param practitionerId - ID of the practitioner
63
- * @param eventData - Calendar event data
64
- * @returns Created calendar event
65
- */
66
- async createPractitionerCalendarEvent(
67
- practitionerId: string,
68
- eventData: Omit<CreateCalendarEventData, "id" | "createdAt" | "updatedAt">
69
- ): Promise<CalendarEvent> {
70
- return createPractitionerCalendarEventUtil(
71
- this.db,
72
- practitionerId,
73
- eventData,
74
- this.generateId.bind(this)
75
- );
76
- }
77
-
78
- /**
79
- * Creates a calendar event for a patient
80
- * @param patientId - ID of the patient
81
- * @param eventData - Calendar event data
82
- * @returns Created calendar event
83
- */
84
- async createPatientCalendarEvent(
85
- patientId: string,
86
- eventData: Omit<CreateCalendarEventData, "id" | "createdAt" | "updatedAt">
87
- ): Promise<CalendarEvent> {
88
- return createPatientCalendarEventUtil(
89
- this.db,
90
- patientId,
91
- eventData,
92
- this.generateId.bind(this)
93
- );
94
- }
95
-
96
- /**
97
- * Creates a calendar event for a clinic
98
- * @param clinicId - ID of the clinic
99
- * @param eventData - Calendar event data
100
- * @returns Created calendar event
101
- */
102
- async createClinicCalendarEvent(
103
- clinicId: string,
104
- eventData: Omit<CreateCalendarEventData, "id" | "createdAt" | "updatedAt">
105
- ): Promise<CalendarEvent> {
106
- return createClinicCalendarEventUtil(
107
- this.db,
108
- clinicId,
109
- eventData,
110
- this.generateId.bind(this)
111
- );
112
- }
113
-
114
- /**
115
- * Creates an appointment across all relevant calendars (practitioner, patient, clinic)
116
- * @param clinicId - ID of the clinic
117
- * @param practitionerId - ID of the practitioner
118
- * @param patientId - ID of the patient
119
- * @param eventData - Calendar event data
120
- * @param syncWithExternalCalendars - Whether to sync with external calendars
121
- * @returns Created calendar event
122
- */
123
- async createAppointment(
124
- clinicId: string,
125
- practitionerId: string,
126
- patientId: string,
127
- eventData: Omit<
128
- CreateCalendarEventData,
129
- | "id"
130
- | "createdAt"
131
- | "updatedAt"
132
- | "clinicBranchId"
133
- | "practitionerProfileId"
134
- | "patientProfileId"
135
- >,
136
- syncWithExternalCalendars: boolean = false
137
- ): Promise<CalendarEvent> {
138
- // Generate a single ID to be used across all calendars
139
- const eventId = this.generateId();
140
-
141
- // Prepare the event data with all required IDs
142
- const appointmentData: Omit<
143
- CreateCalendarEventData,
144
- "id" | "createdAt" | "updatedAt"
145
- > = {
146
- ...eventData,
147
- clinicBranchId: clinicId,
148
- practitionerProfileId: practitionerId,
149
- patientProfileId: patientId,
150
- eventType: CalendarEventType.APPOINTMENT,
151
- // Set sync status to INTERNAL as this is created in the app ecosystem
152
- syncStatus: CalendarSyncStatus.INTERNAL,
153
- };
154
-
155
- // Create the event in all three calendars
156
- const clinicPromise = this.createClinicCalendarEvent(
157
- clinicId,
158
- appointmentData
159
- );
160
-
161
- const practitionerPromise = this.createPractitionerCalendarEvent(
162
- practitionerId,
163
- appointmentData
164
- );
165
-
166
- const patientPromise = this.createPatientCalendarEvent(
167
- patientId,
168
- appointmentData
169
- );
170
-
171
- // Wait for all operations to complete
172
- const [clinicEvent] = await Promise.all([
173
- clinicPromise,
174
- practitionerPromise,
175
- patientPromise,
176
- ]);
177
-
178
- // Sync with external calendars if requested
179
- if (syncWithExternalCalendars) {
180
- try {
181
- await this.syncAppointmentWithExternalCalendars(
182
- clinicId,
183
- practitionerId,
184
- patientId,
185
- clinicEvent.id
186
- );
187
- } catch (error) {
188
- console.error(
189
- "Error syncing appointment with external calendars:",
190
- error
191
- );
192
- // Continue even if sync fails
193
- }
194
- }
195
-
196
- // Return the event from the clinic calendar
197
- return clinicEvent;
198
- }
199
-
200
- /**
201
- * Gets a calendar event for a practitioner
202
- * @param practitionerId - ID of the practitioner
203
- * @param eventId - ID of the event
204
- * @returns Calendar event or null if not found
205
- */
206
- async getPractitionerCalendarEvent(
207
- practitionerId: string,
208
- eventId: string
209
- ): Promise<CalendarEvent | null> {
210
- return getPractitionerCalendarEventUtil(this.db, practitionerId, eventId);
211
- }
212
-
213
- /**
214
- * Gets a calendar event for a patient
215
- * @param patientId - ID of the patient
216
- * @param eventId - ID of the event
217
- * @returns Calendar event or null if not found
218
- */
219
- async getPatientCalendarEvent(
220
- patientId: string,
221
- eventId: string
222
- ): Promise<CalendarEvent | null> {
223
- return getPatientCalendarEventUtil(this.db, patientId, eventId);
224
- }
225
-
226
- /**
227
- * Gets a calendar event for a clinic
228
- * @param clinicId - ID of the clinic
229
- * @param eventId - ID of the event
230
- * @returns Calendar event or null if not found
231
- */
232
- async getClinicCalendarEvent(
233
- clinicId: string,
234
- eventId: string
235
- ): Promise<CalendarEvent | null> {
236
- return getClinicCalendarEventUtil(this.db, clinicId, eventId);
237
- }
238
-
239
- /**
240
- * Gets calendar events for a practitioner within a date range
241
- * @param practitionerId - ID of the practitioner
242
- * @param startDate - Start date of the range
243
- * @param endDate - End date of the range
244
- * @returns Array of calendar events
245
- */
246
- async getPractitionerCalendarEvents(
247
- practitionerId: string,
248
- startDate: Date,
249
- endDate: Date
250
- ): Promise<CalendarEvent[]> {
251
- return getPractitionerCalendarEventsUtil(
252
- this.db,
253
- practitionerId,
254
- startDate,
255
- endDate
256
- );
257
- }
258
-
259
- /**
260
- * Gets calendar events for a patient within a date range
261
- * @param patientId - ID of the patient
262
- * @param startDate - Start date of the range
263
- * @param endDate - End date of the range
264
- * @returns Array of calendar events
265
- */
266
- async getPatientCalendarEvents(
267
- patientId: string,
268
- startDate: Date,
269
- endDate: Date
270
- ): Promise<CalendarEvent[]> {
271
- return getPatientCalendarEventsUtil(this.db, patientId, startDate, endDate);
272
- }
273
-
274
- /**
275
- * Gets calendar events for a clinic within a date range
276
- * @param clinicId - ID of the clinic
277
- * @param startDate - Start date of the range
278
- * @param endDate - End date of the range
279
- * @returns Array of calendar events
280
- */
281
- async getClinicCalendarEvents(
282
- clinicId: string,
283
- startDate: Date,
284
- endDate: Date
285
- ): Promise<CalendarEvent[]> {
286
- return getClinicCalendarEventsUtil(this.db, clinicId, startDate, endDate);
287
- }
288
-
289
- /**
290
- * Updates a calendar event for a practitioner
291
- * @param practitionerId - ID of the practitioner
292
- * @param eventId - ID of the event
293
- * @param updateData - Data to update
294
- * @returns Updated calendar event
295
- */
296
- async updatePractitionerCalendarEvent(
297
- practitionerId: string,
298
- eventId: string,
299
- updateData: Omit<UpdateCalendarEventData, "updatedAt">
300
- ): Promise<CalendarEvent> {
301
- return updatePractitionerCalendarEventUtil(
302
- this.db,
303
- practitionerId,
304
- eventId,
305
- updateData
306
- );
307
- }
308
-
309
- /**
310
- * Updates a calendar event for a patient
311
- * @param patientId - ID of the patient
312
- * @param eventId - ID of the event
313
- * @param updateData - Data to update
314
- * @returns Updated calendar event
315
- */
316
- async updatePatientCalendarEvent(
317
- patientId: string,
318
- eventId: string,
319
- updateData: Omit<UpdateCalendarEventData, "updatedAt">
320
- ): Promise<CalendarEvent> {
321
- return updatePatientCalendarEventUtil(
322
- this.db,
323
- patientId,
324
- eventId,
325
- updateData
326
- );
327
- }
328
-
329
- /**
330
- * Updates a calendar event for a clinic
331
- * @param clinicId - ID of the clinic
332
- * @param eventId - ID of the event
333
- * @param updateData - Data to update
334
- * @returns Updated calendar event
335
- */
336
- async updateClinicCalendarEvent(
337
- clinicId: string,
338
- eventId: string,
339
- updateData: Omit<UpdateCalendarEventData, "updatedAt">
340
- ): Promise<CalendarEvent> {
341
- return updateClinicCalendarEventUtil(
342
- this.db,
343
- clinicId,
344
- eventId,
345
- updateData
346
- );
347
- }
348
-
349
- /**
350
- * Updates an appointment across all relevant calendars (practitioner, patient, clinic)
351
- * @param clinicId - ID of the clinic
352
- * @param practitionerId - ID of the practitioner
353
- * @param patientId - ID of the patient
354
- * @param eventId - ID of the event
355
- * @param updateData - Data to update
356
- * @param syncWithExternalCalendars - Whether to sync with external calendars
357
- * @returns Updated calendar event
358
- */
359
- async updateAppointment(
360
- clinicId: string,
361
- practitionerId: string,
362
- patientId: string,
363
- eventId: string,
364
- updateData: Omit<UpdateCalendarEventData, "updatedAt">,
365
- syncWithExternalCalendars: boolean = false
366
- ): Promise<CalendarEvent> {
367
- // Update the event in all three calendars
368
- const clinicPromise = this.updateClinicCalendarEvent(
369
- clinicId,
370
- eventId,
371
- updateData
372
- );
373
-
374
- const practitionerPromise = this.updatePractitionerCalendarEvent(
375
- practitionerId,
376
- eventId,
377
- updateData
378
- );
379
-
380
- const patientPromise = this.updatePatientCalendarEvent(
381
- patientId,
382
- eventId,
383
- updateData
384
- );
385
-
386
- // Wait for all operations to complete
387
- const [clinicEvent] = await Promise.all([
388
- clinicPromise,
389
- practitionerPromise,
390
- patientPromise,
391
- ]);
392
-
393
- // Sync with external calendars if requested
394
- if (syncWithExternalCalendars) {
395
- try {
396
- await this.syncAppointmentWithExternalCalendars(
397
- clinicId,
398
- practitionerId,
399
- patientId,
400
- eventId
401
- );
402
- } catch (error) {
403
- console.error(
404
- "Error syncing appointment with external calendars:",
405
- error
406
- );
407
- // Continue even if sync fails
408
- }
409
- }
410
-
411
- // Return the event from the clinic calendar
412
- return clinicEvent;
413
- }
414
-
415
- /**
416
- * Deletes a calendar event for a practitioner
417
- * @param practitionerId - ID of the practitioner
418
- * @param eventId - ID of the event
419
- */
420
- async deletePractitionerCalendarEvent(
421
- practitionerId: string,
422
- eventId: string
423
- ): Promise<void> {
424
- return deletePractitionerCalendarEventUtil(
425
- this.db,
426
- practitionerId,
427
- eventId
428
- );
429
- }
430
-
431
- /**
432
- * Deletes a calendar event for a patient
433
- * @param patientId - ID of the patient
434
- * @param eventId - ID of the event
435
- */
436
- async deletePatientCalendarEvent(
437
- patientId: string,
438
- eventId: string
439
- ): Promise<void> {
440
- return deletePatientCalendarEventUtil(this.db, patientId, eventId);
441
- }
442
-
443
- /**
444
- * Deletes a calendar event for a clinic
445
- * @param clinicId - ID of the clinic
446
- * @param eventId - ID of the event
447
- */
448
- async deleteClinicCalendarEvent(
449
- clinicId: string,
450
- eventId: string
451
- ): Promise<void> {
452
- return deleteClinicCalendarEventUtil(this.db, clinicId, eventId);
453
- }
454
-
455
- /**
456
- * Deletes an appointment across all relevant calendars (practitioner, patient, clinic)
457
- * @param clinicId - ID of the clinic
458
- * @param practitionerId - ID of the practitioner
459
- * @param patientId - ID of the patient
460
- * @param eventId - ID of the event
461
- */
462
- async deleteAppointment(
463
- clinicId: string,
464
- practitionerId: string,
465
- patientId: string,
466
- eventId: string
467
- ): Promise<void> {
468
- // First get the appointment to check if it's synced with external calendars
469
- const appointment = await this.getClinicCalendarEvent(clinicId, eventId);
470
-
471
- if (appointment && appointment.syncStatus === CalendarSyncStatus.EXTERNAL) {
472
- // Delete from external calendars
473
- await this.deleteEventFromExternalCalendars(
474
- appointment,
475
- "clinic",
476
- clinicId
477
- );
478
-
479
- // Also try to delete from practitioner and patient external calendars
480
- const practitionerEvent = await this.getPractitionerCalendarEvent(
481
- practitionerId,
482
- eventId
483
- );
484
- if (practitionerEvent) {
485
- await this.deleteEventFromExternalCalendars(
486
- practitionerEvent,
487
- "practitioner",
488
- practitionerId
489
- );
490
- }
491
-
492
- const patientEvent = await this.getPatientCalendarEvent(
493
- patientId,
494
- eventId
495
- );
496
- if (patientEvent) {
497
- await this.deleteEventFromExternalCalendars(
498
- patientEvent,
499
- "patient",
500
- patientId
501
- );
502
- }
503
- }
504
-
505
- // Delete the event from all three calendars
506
- const clinicPromise = this.deleteClinicCalendarEvent(clinicId, eventId);
507
-
508
- const practitionerPromise = this.deletePractitionerCalendarEvent(
509
- practitionerId,
510
- eventId
511
- );
512
-
513
- const patientPromise = this.deletePatientCalendarEvent(patientId, eventId);
514
-
515
- // Wait for all operations to complete
516
- await Promise.all([clinicPromise, practitionerPromise, patientPromise]);
517
- }
518
-
519
- /**
520
- * Gets pending appointments for a clinic
521
- * @param clinicId - ID of the clinic
522
- * @returns Array of pending calendar events
523
- */
524
- async getPendingClinicAppointments(
525
- clinicId: string
526
- ): Promise<CalendarEvent[]> {
527
- const events = await getClinicCalendarEventsUtil(
528
- this.db,
529
- clinicId,
530
- new Date(0), // From the beginning of time
531
- new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // To one year from now
532
- );
533
-
534
- return events.filter(
535
- (event: CalendarEvent) =>
536
- event.status === CalendarEventStatus.PENDING &&
537
- event.eventType === CalendarEventType.APPOINTMENT
538
- );
539
- }
540
-
541
- /**
542
- * Gets confirmed appointments for a practitioner
543
- * @param practitionerId - ID of the practitioner
544
- * @returns Array of confirmed calendar events
545
- */
546
- async getConfirmedPractitionerAppointments(
547
- practitionerId: string
548
- ): Promise<CalendarEvent[]> {
549
- const events = await getPractitionerCalendarEventsUtil(
550
- this.db,
551
- practitionerId,
552
- new Date(0), // From the beginning of time
553
- new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // To one year from now
554
- );
555
-
556
- return events.filter(
557
- (event: CalendarEvent) =>
558
- event.status === CalendarEventStatus.CONFIRMED &&
559
- event.eventType === CalendarEventType.APPOINTMENT
560
- );
561
- }
562
-
563
- /**
564
- * Gets upcoming appointments for a patient
565
- * @param patientId - ID of the patient
566
- * @returns Array of upcoming calendar events
567
- */
568
- async getUpcomingPatientAppointments(
569
- patientId: string
570
- ): Promise<CalendarEvent[]> {
571
- const now = new Date();
572
- const events = await getPatientCalendarEventsUtil(
573
- this.db,
574
- patientId,
575
- now,
576
- new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // To one year from now
577
- );
578
-
579
- return events.filter(
580
- (event: CalendarEvent) =>
581
- event.eventType === CalendarEventType.APPOINTMENT
582
- );
583
- }
584
-
585
- /**
586
- * Confirms an appointment
587
- * @param clinicId - ID of the clinic
588
- * @param practitionerId - ID of the practitioner
589
- * @param patientId - ID of the patient
590
- * @param eventId - ID of the event
591
- * @param syncWithExternalCalendars - Whether to sync with external calendars
592
- * @returns Confirmed calendar event
593
- */
594
- async confirmAppointment(
595
- clinicId: string,
596
- practitionerId: string,
597
- patientId: string,
598
- eventId: string,
599
- syncWithExternalCalendars: boolean = true
600
- ): Promise<CalendarEvent> {
601
- return this.updateAppointment(
602
- clinicId,
603
- practitionerId,
604
- patientId,
605
- eventId,
606
- { status: CalendarEventStatus.CONFIRMED },
607
- syncWithExternalCalendars
608
- );
609
- }
610
-
611
- /**
612
- * Rejects an appointment
613
- * @param clinicId - ID of the clinic
614
- * @param practitionerId - ID of the practitioner
615
- * @param patientId - ID of the patient
616
- * @param eventId - ID of the event
617
- * @param syncWithExternalCalendars - Whether to sync with external calendars
618
- * @returns Rejected calendar event
619
- */
620
- async rejectAppointment(
621
- clinicId: string,
622
- practitionerId: string,
623
- patientId: string,
624
- eventId: string,
625
- syncWithExternalCalendars: boolean = true
626
- ): Promise<CalendarEvent> {
627
- return this.updateAppointment(
628
- clinicId,
629
- practitionerId,
630
- patientId,
631
- eventId,
632
- { status: CalendarEventStatus.REJECTED },
633
- syncWithExternalCalendars
634
- );
635
- }
636
-
637
- /**
638
- * Cancels an appointment
639
- * @param clinicId - ID of the clinic
640
- * @param practitionerId - ID of the practitioner
641
- * @param patientId - ID of the patient
642
- * @param eventId - ID of the event
643
- * @param syncWithExternalCalendars - Whether to sync with external calendars
644
- * @returns Canceled calendar event
645
- */
646
- async cancelAppointment(
647
- clinicId: string,
648
- practitionerId: string,
649
- patientId: string,
650
- eventId: string,
651
- syncWithExternalCalendars: boolean = true
652
- ): Promise<CalendarEvent> {
653
- return this.updateAppointment(
654
- clinicId,
655
- practitionerId,
656
- patientId,
657
- eventId,
658
- { status: CalendarEventStatus.CANCELED },
659
- syncWithExternalCalendars
660
- );
661
- }
662
-
663
- /**
664
- * Reschedules an appointment
665
- * @param clinicId - ID of the clinic
666
- * @param practitionerId - ID of the practitioner
667
- * @param patientId - ID of the patient
668
- * @param eventId - ID of the event
669
- * @param newEventTime - New event time
670
- * @param syncWithExternalCalendars - Whether to sync with external calendars
671
- * @returns Rescheduled calendar event
672
- */
673
- async rescheduleAppointment(
674
- clinicId: string,
675
- practitionerId: string,
676
- patientId: string,
677
- eventId: string,
678
- newEventTime: CalendarEventTime,
679
- syncWithExternalCalendars: boolean = true
680
- ): Promise<CalendarEvent> {
681
- // First get the current event to preserve any other fields
682
- const currentEvent = await this.getClinicCalendarEvent(clinicId, eventId);
683
-
684
- if (!currentEvent) {
685
- throw new Error(`Appointment with ID ${eventId} not found`);
686
- }
687
-
688
- // Update the appointment with new time and status
689
- return this.updateAppointment(
690
- clinicId,
691
- practitionerId,
692
- patientId,
693
- eventId,
694
- {
695
- status: CalendarEventStatus.RESCHEDULED,
696
- eventTime: newEventTime,
697
- },
698
- syncWithExternalCalendars
699
- );
700
- }
701
-
702
- /**
703
- * Gets the SyncedCalendarsService for managing synced calendars
704
- * @returns SyncedCalendarsService instance
705
- */
706
- getSyncedCalendarsService(): SyncedCalendarsService {
707
- return this.syncedCalendarsService;
708
- }
709
-
710
- /**
711
- * Syncs an appointment with external calendars
712
- * @param clinicId - ID of the clinic
713
- * @param practitionerId - ID of the practitioner
714
- * @param patientId - ID of the patient
715
- * @param eventId - ID of the event
716
- * @returns Updated calendar event with sync information
717
- */
718
- async syncAppointmentWithExternalCalendars(
719
- clinicId: string,
720
- practitionerId: string,
721
- patientId: string,
722
- eventId: string
723
- ): Promise<CalendarEvent> {
724
- // Get the appointment
725
- const appointment = await this.getClinicCalendarEvent(clinicId, eventId);
726
-
727
- if (!appointment) {
728
- throw new Error(`Appointment with ID ${eventId} not found`);
729
- }
730
-
731
- // Get synced calendars for all entities
732
- const clinicCalendars =
733
- await this.syncedCalendarsService.getClinicSyncedCalendars(clinicId);
734
- const practitionerCalendars =
735
- await this.syncedCalendarsService.getPractitionerSyncedCalendars(
736
- practitionerId
737
- );
738
- const patientCalendars =
739
- await this.syncedCalendarsService.getPatientSyncedCalendars(patientId);
740
-
741
- // Combine all active calendars
742
- const activeCalendars = [
743
- ...clinicCalendars.filter((cal) => cal.isActive),
744
- ...practitionerCalendars.filter((cal) => cal.isActive),
745
- ...patientCalendars.filter((cal) => cal.isActive),
746
- ];
747
-
748
- // If no active calendars, return the appointment as is
749
- if (activeCalendars.length === 0) {
750
- return appointment;
751
- }
752
-
753
- // Array to store synced calendar event IDs
754
- const syncedCalendarEvents: SyncedCalendarEvent[] = [];
755
- const currentTime = Timestamp.now();
756
-
757
- // Sync with each calendar
758
- for (const calendar of activeCalendars) {
759
- try {
760
- // Sync based on provider
761
- if (calendar.provider === SyncedCalendarProvider.GOOGLE) {
762
- let syncResult;
763
-
764
- // Determine which entity owns this calendar and sync accordingly
765
- if (clinicCalendars.some((c) => c.id === calendar.id)) {
766
- syncResult =
767
- await this.syncedCalendarsService.syncClinicEventsToGoogleCalendar(
768
- clinicId,
769
- calendar.id,
770
- [appointment]
771
- );
772
- } else if (practitionerCalendars.some((c) => c.id === calendar.id)) {
773
- syncResult =
774
- await this.syncedCalendarsService.syncPractitionerEventsToGoogleCalendar(
775
- practitionerId,
776
- calendar.id,
777
- [appointment]
778
- );
779
- } else if (patientCalendars.some((c) => c.id === calendar.id)) {
780
- syncResult =
781
- await this.syncedCalendarsService.syncPatientEventsToGoogleCalendar(
782
- patientId,
783
- calendar.id,
784
- [appointment]
785
- );
786
- }
787
-
788
- // If sync was successful and we have event IDs, add them to our list
789
- if (syncResult && syncResult.success && syncResult.syncedEvents > 0) {
790
- // Get the external event ID - this might need to be adjusted based on the actual return structure
791
- const externalEventId =
792
- (syncResult as any).eventIds?.[0] || appointment.id;
793
-
794
- syncedCalendarEvents.push({
795
- eventId: externalEventId,
796
- syncedCalendarProvider: SyncedCalendarProvider.GOOGLE,
797
- syncedAt: currentTime,
798
- });
799
- }
800
- }
801
- // Add other providers as needed (Outlook, Apple, etc.)
802
- } catch (error) {
803
- console.error(`Error syncing with calendar ${calendar.id}:`, error);
804
- // Continue with other calendars even if one fails
805
- }
806
- }
807
-
808
- // Update the appointment with synced calendar information
809
- if (syncedCalendarEvents.length > 0) {
810
- return this.updateAppointment(
811
- clinicId,
812
- practitionerId,
813
- patientId,
814
- eventId,
815
- {
816
- syncedCalendarEventId: syncedCalendarEvents,
817
- }
818
- );
819
- }
820
-
821
- return appointment;
822
- }
823
-
824
- /**
825
- * Imports events from external calendars
826
- * @param entityType - Type of entity (practitioner, patient, clinic)
827
- * @param entityId - ID of the entity
828
- * @param startDate - Start date for fetching events
829
- * @param endDate - End date for fetching events
830
- * @returns Number of events imported
831
- */
832
- async importEventsFromExternalCalendars(
833
- entityType: "practitioner" | "patient" | "clinic",
834
- entityId: string,
835
- startDate: Date,
836
- endDate: Date
837
- ): Promise<number> {
838
- let syncedCalendars: any[] = [];
839
-
840
- // Get synced calendars for the entity
841
- switch (entityType) {
842
- case "practitioner":
843
- syncedCalendars =
844
- await this.syncedCalendarsService.getPractitionerSyncedCalendars(
845
- entityId
846
- );
847
- break;
848
- case "patient":
849
- syncedCalendars =
850
- await this.syncedCalendarsService.getPatientSyncedCalendars(entityId);
851
- break;
852
- case "clinic":
853
- syncedCalendars =
854
- await this.syncedCalendarsService.getClinicSyncedCalendars(entityId);
855
- break;
856
- }
857
-
858
- // Filter active calendars
859
- const activeCalendars = syncedCalendars.filter((cal) => cal.isActive);
860
-
861
- if (activeCalendars.length === 0) {
862
- return 0;
863
- }
864
-
865
- let importedEventsCount = 0;
866
- const currentTime = Timestamp.now();
867
-
868
- // Import from each calendar
869
- for (const calendar of activeCalendars) {
870
- try {
871
- let externalEvents: any[] = [];
872
-
873
- // Fetch events based on provider and entity type
874
- if (calendar.provider === SyncedCalendarProvider.GOOGLE) {
875
- switch (entityType) {
876
- case "practitioner":
877
- externalEvents =
878
- await this.syncedCalendarsService.fetchEventsFromPractitionerGoogleCalendar(
879
- entityId,
880
- calendar.id,
881
- startDate,
882
- endDate
883
- );
884
- break;
885
- case "patient":
886
- externalEvents =
887
- await this.syncedCalendarsService.fetchEventsFromPatientGoogleCalendar(
888
- entityId,
889
- calendar.id,
890
- startDate,
891
- endDate
892
- );
893
- break;
894
- case "clinic":
895
- externalEvents =
896
- await this.syncedCalendarsService.fetchEventsFromClinicGoogleCalendar(
897
- entityId,
898
- calendar.id,
899
- startDate,
900
- endDate
901
- );
902
- break;
903
- }
904
- }
905
- // Add other providers as needed
906
-
907
- // Process and import each event
908
- for (const externalEvent of externalEvents) {
909
- try {
910
- // Create event data from external event
911
- const eventData: Omit<
912
- CreateCalendarEventData,
913
- "id" | "createdAt" | "updatedAt"
914
- > = {
915
- eventName: externalEvent.summary || "Imported Event",
916
- eventTime: {
917
- start: Timestamp.fromDate(
918
- new Date(
919
- externalEvent.start.dateTime || externalEvent.start.date
920
- )
921
- ),
922
- end: Timestamp.fromDate(
923
- new Date(externalEvent.end.dateTime || externalEvent.end.date)
924
- ),
925
- },
926
- description: externalEvent.description || "",
927
- status: CalendarEventStatus.CONFIRMED,
928
- // Set sync status to EXTERNAL as this is imported from an external calendar
929
- syncStatus: CalendarSyncStatus.EXTERNAL,
930
- eventType: CalendarEventType.BLOCKING, // Use BLOCKING type for external events
931
- syncedCalendarEventId: [
932
- {
933
- eventId: externalEvent.id,
934
- syncedCalendarProvider: calendar.provider,
935
- syncedAt: currentTime,
936
- },
937
- ],
938
- };
939
-
940
- // Create the event in our system
941
- switch (entityType) {
942
- case "practitioner":
943
- await this.createPractitionerCalendarEvent(entityId, eventData);
944
- break;
945
- case "patient":
946
- await this.createPatientCalendarEvent(entityId, eventData);
947
- break;
948
- case "clinic":
949
- await this.createClinicCalendarEvent(entityId, eventData);
950
- break;
951
- }
952
-
953
- importedEventsCount++;
954
- } catch (eventError) {
955
- console.error("Error importing event:", eventError);
956
- // Continue with other events even if one fails
957
- }
958
- }
959
- } catch (calendarError) {
960
- console.error(
961
- `Error fetching events from calendar ${calendar.id}:`,
962
- calendarError
963
- );
964
- // Continue with other calendars even if one fails
965
- }
966
- }
967
-
968
- return importedEventsCount;
969
- }
970
-
971
- /**
972
- * Deletes an event from external calendars
973
- * @param event - Calendar event to delete
974
- * @param entityType - Type of entity (practitioner, patient, clinic)
975
- * @param entityId - ID of the entity
976
- * @returns Success status
977
- */
978
- async deleteEventFromExternalCalendars(
979
- event: CalendarEvent,
980
- entityType: "practitioner" | "patient" | "clinic",
981
- entityId: string
982
- ): Promise<boolean> {
983
- // If the event doesn't have synced calendar event IDs, return success
984
- if (
985
- !event.syncedCalendarEventId ||
986
- event.syncedCalendarEventId.length === 0
987
- ) {
988
- return true;
989
- }
990
-
991
- // Get synced calendars for the entity
992
- let syncedCalendars: any[] = [];
993
- switch (entityType) {
994
- case "practitioner":
995
- syncedCalendars =
996
- await this.syncedCalendarsService.getPractitionerSyncedCalendars(
997
- entityId
998
- );
999
- break;
1000
- case "patient":
1001
- syncedCalendars =
1002
- await this.syncedCalendarsService.getPatientSyncedCalendars(entityId);
1003
- break;
1004
- case "clinic":
1005
- syncedCalendars =
1006
- await this.syncedCalendarsService.getClinicSyncedCalendars(entityId);
1007
- break;
1008
- }
1009
-
1010
- // Filter active calendars
1011
- const activeCalendars = syncedCalendars.filter((cal) => cal.isActive);
1012
-
1013
- if (activeCalendars.length === 0) {
1014
- return true;
1015
- }
1016
-
1017
- let allSuccessful = true;
1018
-
1019
- // Delete from each external calendar
1020
- for (const syncedEvent of event.syncedCalendarEventId) {
1021
- try {
1022
- // Find the calendar for this provider
1023
- const calendar = activeCalendars.find(
1024
- (cal) => cal.provider === syncedEvent.syncedCalendarProvider
1025
- );
1026
-
1027
- if (!calendar) {
1028
- continue;
1029
- }
1030
-
1031
- // Delete based on provider and entity type
1032
- if (
1033
- syncedEvent.syncedCalendarProvider === SyncedCalendarProvider.GOOGLE
1034
- ) {
1035
- let success = false;
1036
-
1037
- switch (entityType) {
1038
- case "practitioner":
1039
- success =
1040
- await this.syncedCalendarsService.deletePractitionerGoogleCalendarEvent(
1041
- entityId,
1042
- calendar.id,
1043
- syncedEvent.eventId
1044
- );
1045
- break;
1046
- case "patient":
1047
- success =
1048
- await this.syncedCalendarsService.deletePatientGoogleCalendarEvent(
1049
- entityId,
1050
- calendar.id,
1051
- syncedEvent.eventId
1052
- );
1053
- break;
1054
- case "clinic":
1055
- success =
1056
- await this.syncedCalendarsService.deleteClinicGoogleCalendarEvent(
1057
- entityId,
1058
- calendar.id,
1059
- syncedEvent.eventId
1060
- );
1061
- break;
1062
- }
1063
-
1064
- if (!success) {
1065
- allSuccessful = false;
1066
- }
1067
- }
1068
- // Add other providers as needed
1069
- } catch (error) {
1070
- console.error(`Error deleting event from external calendar:`, error);
1071
- allSuccessful = false;
1072
- }
1073
- }
1074
-
1075
- return allSuccessful;
1076
- }
1077
- }
1
+ import { Auth } from "firebase/auth";
2
+ import { Firestore, Timestamp } from "firebase/firestore";
3
+ import { FirebaseApp } from "firebase/app";
4
+ import { BaseService } from "../base.service";
5
+ import {
6
+ CalendarEvent,
7
+ CalendarEventStatus,
8
+ CalendarEventTime,
9
+ CalendarEventType,
10
+ CalendarSyncStatus,
11
+ CreateCalendarEventData,
12
+ UpdateCalendarEventData,
13
+ CALENDAR_COLLECTION,
14
+ SyncedCalendarEvent,
15
+ } from "../../types/calendar";
16
+ import { PRACTITIONERS_COLLECTION } from "../../types/practitioner";
17
+ import { PATIENTS_COLLECTION } from "../../types/patient";
18
+ import { CLINICS_COLLECTION } from "../../types/clinic";
19
+ import { SyncedCalendarProvider } from "../../types/calendar/synced-calendar.types";
20
+
21
+ // Import utility functions from the index file
22
+ import {
23
+ createPractitionerCalendarEventUtil,
24
+ createPatientCalendarEventUtil,
25
+ createClinicCalendarEventUtil,
26
+ getPractitionerCalendarEventUtil,
27
+ getPatientCalendarEventUtil,
28
+ getClinicCalendarEventUtil,
29
+ getPractitionerCalendarEventsUtil,
30
+ getPatientCalendarEventsUtil,
31
+ getClinicCalendarEventsUtil,
32
+ updatePractitionerCalendarEventUtil,
33
+ updatePatientCalendarEventUtil,
34
+ updateClinicCalendarEventUtil,
35
+ deletePractitionerCalendarEventUtil,
36
+ deletePatientCalendarEventUtil,
37
+ deleteClinicCalendarEventUtil,
38
+ } from "./utils";
39
+
40
+ // Import the SyncedCalendarsService for calendar synchronization
41
+ import { SyncedCalendarsService } from "./synced-calendars.service";
42
+
43
+ /**
44
+ * Service for managing calendar events
45
+ */
46
+ export class CalendarService extends BaseService {
47
+ private syncedCalendarsService: SyncedCalendarsService;
48
+
49
+ /**
50
+ * Creates a new CalendarService instance
51
+ * @param db - Firestore instance
52
+ * @param auth - Firebase Auth instance
53
+ * @param app - Firebase App instance
54
+ */
55
+ constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
56
+ super(db, auth, app);
57
+ this.syncedCalendarsService = new SyncedCalendarsService(db, auth, app);
58
+ }
59
+
60
+ /**
61
+ * Creates a calendar event for a practitioner
62
+ * @param practitionerId - ID of the practitioner
63
+ * @param eventData - Calendar event data
64
+ * @returns Created calendar event
65
+ */
66
+ async createPractitionerCalendarEvent(
67
+ practitionerId: string,
68
+ eventData: Omit<CreateCalendarEventData, "id" | "createdAt" | "updatedAt">
69
+ ): Promise<CalendarEvent> {
70
+ return createPractitionerCalendarEventUtil(
71
+ this.db,
72
+ practitionerId,
73
+ eventData,
74
+ this.generateId.bind(this)
75
+ );
76
+ }
77
+
78
+ /**
79
+ * Creates a calendar event for a patient
80
+ * @param patientId - ID of the patient
81
+ * @param eventData - Calendar event data
82
+ * @returns Created calendar event
83
+ */
84
+ async createPatientCalendarEvent(
85
+ patientId: string,
86
+ eventData: Omit<CreateCalendarEventData, "id" | "createdAt" | "updatedAt">
87
+ ): Promise<CalendarEvent> {
88
+ return createPatientCalendarEventUtil(
89
+ this.db,
90
+ patientId,
91
+ eventData,
92
+ this.generateId.bind(this)
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Creates a calendar event for a clinic
98
+ * @param clinicId - ID of the clinic
99
+ * @param eventData - Calendar event data
100
+ * @returns Created calendar event
101
+ */
102
+ async createClinicCalendarEvent(
103
+ clinicId: string,
104
+ eventData: Omit<CreateCalendarEventData, "id" | "createdAt" | "updatedAt">
105
+ ): Promise<CalendarEvent> {
106
+ return createClinicCalendarEventUtil(
107
+ this.db,
108
+ clinicId,
109
+ eventData,
110
+ this.generateId.bind(this)
111
+ );
112
+ }
113
+
114
+ /**
115
+ * Creates an appointment across all relevant calendars (practitioner, patient, clinic)
116
+ * @param clinicId - ID of the clinic
117
+ * @param practitionerId - ID of the practitioner
118
+ * @param patientId - ID of the patient
119
+ * @param eventData - Calendar event data
120
+ * @param syncWithExternalCalendars - Whether to sync with external calendars
121
+ * @returns Created calendar event
122
+ */
123
+ async createAppointment(
124
+ clinicId: string,
125
+ practitionerId: string,
126
+ patientId: string,
127
+ eventData: Omit<
128
+ CreateCalendarEventData,
129
+ | "id"
130
+ | "createdAt"
131
+ | "updatedAt"
132
+ | "clinicBranchId"
133
+ | "practitionerProfileId"
134
+ | "patientProfileId"
135
+ >,
136
+ syncWithExternalCalendars: boolean = false
137
+ ): Promise<CalendarEvent> {
138
+ // Generate a single ID to be used across all calendars
139
+ const eventId = this.generateId();
140
+
141
+ // Prepare the event data with all required IDs
142
+ const appointmentData: Omit<
143
+ CreateCalendarEventData,
144
+ "id" | "createdAt" | "updatedAt"
145
+ > = {
146
+ ...eventData,
147
+ clinicBranchId: clinicId,
148
+ practitionerProfileId: practitionerId,
149
+ patientProfileId: patientId,
150
+ eventType: CalendarEventType.APPOINTMENT,
151
+ // Set sync status to INTERNAL as this is created in the app ecosystem
152
+ syncStatus: CalendarSyncStatus.INTERNAL,
153
+ };
154
+
155
+ // Create the event in all three calendars
156
+ const clinicPromise = this.createClinicCalendarEvent(
157
+ clinicId,
158
+ appointmentData
159
+ );
160
+
161
+ const practitionerPromise = this.createPractitionerCalendarEvent(
162
+ practitionerId,
163
+ appointmentData
164
+ );
165
+
166
+ const patientPromise = this.createPatientCalendarEvent(
167
+ patientId,
168
+ appointmentData
169
+ );
170
+
171
+ // Wait for all operations to complete
172
+ const [clinicEvent] = await Promise.all([
173
+ clinicPromise,
174
+ practitionerPromise,
175
+ patientPromise,
176
+ ]);
177
+
178
+ // Sync with external calendars if requested
179
+ if (syncWithExternalCalendars) {
180
+ try {
181
+ await this.syncAppointmentWithExternalCalendars(
182
+ clinicId,
183
+ practitionerId,
184
+ patientId,
185
+ clinicEvent.id
186
+ );
187
+ } catch (error) {
188
+ console.error(
189
+ "Error syncing appointment with external calendars:",
190
+ error
191
+ );
192
+ // Continue even if sync fails
193
+ }
194
+ }
195
+
196
+ // Return the event from the clinic calendar
197
+ return clinicEvent;
198
+ }
199
+
200
+ /**
201
+ * Gets a calendar event for a practitioner
202
+ * @param practitionerId - ID of the practitioner
203
+ * @param eventId - ID of the event
204
+ * @returns Calendar event or null if not found
205
+ */
206
+ async getPractitionerCalendarEvent(
207
+ practitionerId: string,
208
+ eventId: string
209
+ ): Promise<CalendarEvent | null> {
210
+ return getPractitionerCalendarEventUtil(this.db, practitionerId, eventId);
211
+ }
212
+
213
+ /**
214
+ * Gets a calendar event for a patient
215
+ * @param patientId - ID of the patient
216
+ * @param eventId - ID of the event
217
+ * @returns Calendar event or null if not found
218
+ */
219
+ async getPatientCalendarEvent(
220
+ patientId: string,
221
+ eventId: string
222
+ ): Promise<CalendarEvent | null> {
223
+ return getPatientCalendarEventUtil(this.db, patientId, eventId);
224
+ }
225
+
226
+ /**
227
+ * Gets a calendar event for a clinic
228
+ * @param clinicId - ID of the clinic
229
+ * @param eventId - ID of the event
230
+ * @returns Calendar event or null if not found
231
+ */
232
+ async getClinicCalendarEvent(
233
+ clinicId: string,
234
+ eventId: string
235
+ ): Promise<CalendarEvent | null> {
236
+ return getClinicCalendarEventUtil(this.db, clinicId, eventId);
237
+ }
238
+
239
+ /**
240
+ * Gets calendar events for a practitioner within a date range
241
+ * @param practitionerId - ID of the practitioner
242
+ * @param startDate - Start date of the range
243
+ * @param endDate - End date of the range
244
+ * @returns Array of calendar events
245
+ */
246
+ async getPractitionerCalendarEvents(
247
+ practitionerId: string,
248
+ startDate: Date,
249
+ endDate: Date
250
+ ): Promise<CalendarEvent[]> {
251
+ return getPractitionerCalendarEventsUtil(
252
+ this.db,
253
+ practitionerId,
254
+ startDate,
255
+ endDate
256
+ );
257
+ }
258
+
259
+ /**
260
+ * Gets calendar events for a patient within a date range
261
+ * @param patientId - ID of the patient
262
+ * @param startDate - Start date of the range
263
+ * @param endDate - End date of the range
264
+ * @returns Array of calendar events
265
+ */
266
+ async getPatientCalendarEvents(
267
+ patientId: string,
268
+ startDate: Date,
269
+ endDate: Date
270
+ ): Promise<CalendarEvent[]> {
271
+ return getPatientCalendarEventsUtil(this.db, patientId, startDate, endDate);
272
+ }
273
+
274
+ /**
275
+ * Gets calendar events for a clinic within a date range
276
+ * @param clinicId - ID of the clinic
277
+ * @param startDate - Start date of the range
278
+ * @param endDate - End date of the range
279
+ * @returns Array of calendar events
280
+ */
281
+ async getClinicCalendarEvents(
282
+ clinicId: string,
283
+ startDate: Date,
284
+ endDate: Date
285
+ ): Promise<CalendarEvent[]> {
286
+ return getClinicCalendarEventsUtil(this.db, clinicId, startDate, endDate);
287
+ }
288
+
289
+ /**
290
+ * Updates a calendar event for a practitioner
291
+ * @param practitionerId - ID of the practitioner
292
+ * @param eventId - ID of the event
293
+ * @param updateData - Data to update
294
+ * @returns Updated calendar event
295
+ */
296
+ async updatePractitionerCalendarEvent(
297
+ practitionerId: string,
298
+ eventId: string,
299
+ updateData: Omit<UpdateCalendarEventData, "updatedAt">
300
+ ): Promise<CalendarEvent> {
301
+ return updatePractitionerCalendarEventUtil(
302
+ this.db,
303
+ practitionerId,
304
+ eventId,
305
+ updateData
306
+ );
307
+ }
308
+
309
+ /**
310
+ * Updates a calendar event for a patient
311
+ * @param patientId - ID of the patient
312
+ * @param eventId - ID of the event
313
+ * @param updateData - Data to update
314
+ * @returns Updated calendar event
315
+ */
316
+ async updatePatientCalendarEvent(
317
+ patientId: string,
318
+ eventId: string,
319
+ updateData: Omit<UpdateCalendarEventData, "updatedAt">
320
+ ): Promise<CalendarEvent> {
321
+ return updatePatientCalendarEventUtil(
322
+ this.db,
323
+ patientId,
324
+ eventId,
325
+ updateData
326
+ );
327
+ }
328
+
329
+ /**
330
+ * Updates a calendar event for a clinic
331
+ * @param clinicId - ID of the clinic
332
+ * @param eventId - ID of the event
333
+ * @param updateData - Data to update
334
+ * @returns Updated calendar event
335
+ */
336
+ async updateClinicCalendarEvent(
337
+ clinicId: string,
338
+ eventId: string,
339
+ updateData: Omit<UpdateCalendarEventData, "updatedAt">
340
+ ): Promise<CalendarEvent> {
341
+ return updateClinicCalendarEventUtil(
342
+ this.db,
343
+ clinicId,
344
+ eventId,
345
+ updateData
346
+ );
347
+ }
348
+
349
+ /**
350
+ * Updates an appointment across all relevant calendars (practitioner, patient, clinic)
351
+ * @param clinicId - ID of the clinic
352
+ * @param practitionerId - ID of the practitioner
353
+ * @param patientId - ID of the patient
354
+ * @param eventId - ID of the event
355
+ * @param updateData - Data to update
356
+ * @param syncWithExternalCalendars - Whether to sync with external calendars
357
+ * @returns Updated calendar event
358
+ */
359
+ async updateAppointment(
360
+ clinicId: string,
361
+ practitionerId: string,
362
+ patientId: string,
363
+ eventId: string,
364
+ updateData: Omit<UpdateCalendarEventData, "updatedAt">,
365
+ syncWithExternalCalendars: boolean = false
366
+ ): Promise<CalendarEvent> {
367
+ // Update the event in all three calendars
368
+ const clinicPromise = this.updateClinicCalendarEvent(
369
+ clinicId,
370
+ eventId,
371
+ updateData
372
+ );
373
+
374
+ const practitionerPromise = this.updatePractitionerCalendarEvent(
375
+ practitionerId,
376
+ eventId,
377
+ updateData
378
+ );
379
+
380
+ const patientPromise = this.updatePatientCalendarEvent(
381
+ patientId,
382
+ eventId,
383
+ updateData
384
+ );
385
+
386
+ // Wait for all operations to complete
387
+ const [clinicEvent] = await Promise.all([
388
+ clinicPromise,
389
+ practitionerPromise,
390
+ patientPromise,
391
+ ]);
392
+
393
+ // Sync with external calendars if requested
394
+ if (syncWithExternalCalendars) {
395
+ try {
396
+ await this.syncAppointmentWithExternalCalendars(
397
+ clinicId,
398
+ practitionerId,
399
+ patientId,
400
+ eventId
401
+ );
402
+ } catch (error) {
403
+ console.error(
404
+ "Error syncing appointment with external calendars:",
405
+ error
406
+ );
407
+ // Continue even if sync fails
408
+ }
409
+ }
410
+
411
+ // Return the event from the clinic calendar
412
+ return clinicEvent;
413
+ }
414
+
415
+ /**
416
+ * Deletes a calendar event for a practitioner
417
+ * @param practitionerId - ID of the practitioner
418
+ * @param eventId - ID of the event
419
+ */
420
+ async deletePractitionerCalendarEvent(
421
+ practitionerId: string,
422
+ eventId: string
423
+ ): Promise<void> {
424
+ return deletePractitionerCalendarEventUtil(
425
+ this.db,
426
+ practitionerId,
427
+ eventId
428
+ );
429
+ }
430
+
431
+ /**
432
+ * Deletes a calendar event for a patient
433
+ * @param patientId - ID of the patient
434
+ * @param eventId - ID of the event
435
+ */
436
+ async deletePatientCalendarEvent(
437
+ patientId: string,
438
+ eventId: string
439
+ ): Promise<void> {
440
+ return deletePatientCalendarEventUtil(this.db, patientId, eventId);
441
+ }
442
+
443
+ /**
444
+ * Deletes a calendar event for a clinic
445
+ * @param clinicId - ID of the clinic
446
+ * @param eventId - ID of the event
447
+ */
448
+ async deleteClinicCalendarEvent(
449
+ clinicId: string,
450
+ eventId: string
451
+ ): Promise<void> {
452
+ return deleteClinicCalendarEventUtil(this.db, clinicId, eventId);
453
+ }
454
+
455
+ /**
456
+ * Deletes an appointment across all relevant calendars (practitioner, patient, clinic)
457
+ * @param clinicId - ID of the clinic
458
+ * @param practitionerId - ID of the practitioner
459
+ * @param patientId - ID of the patient
460
+ * @param eventId - ID of the event
461
+ */
462
+ async deleteAppointment(
463
+ clinicId: string,
464
+ practitionerId: string,
465
+ patientId: string,
466
+ eventId: string
467
+ ): Promise<void> {
468
+ // First get the appointment to check if it's synced with external calendars
469
+ const appointment = await this.getClinicCalendarEvent(clinicId, eventId);
470
+
471
+ if (appointment && appointment.syncStatus === CalendarSyncStatus.EXTERNAL) {
472
+ // Delete from external calendars
473
+ await this.deleteEventFromExternalCalendars(
474
+ appointment,
475
+ "clinic",
476
+ clinicId
477
+ );
478
+
479
+ // Also try to delete from practitioner and patient external calendars
480
+ const practitionerEvent = await this.getPractitionerCalendarEvent(
481
+ practitionerId,
482
+ eventId
483
+ );
484
+ if (practitionerEvent) {
485
+ await this.deleteEventFromExternalCalendars(
486
+ practitionerEvent,
487
+ "practitioner",
488
+ practitionerId
489
+ );
490
+ }
491
+
492
+ const patientEvent = await this.getPatientCalendarEvent(
493
+ patientId,
494
+ eventId
495
+ );
496
+ if (patientEvent) {
497
+ await this.deleteEventFromExternalCalendars(
498
+ patientEvent,
499
+ "patient",
500
+ patientId
501
+ );
502
+ }
503
+ }
504
+
505
+ // Delete the event from all three calendars
506
+ const clinicPromise = this.deleteClinicCalendarEvent(clinicId, eventId);
507
+
508
+ const practitionerPromise = this.deletePractitionerCalendarEvent(
509
+ practitionerId,
510
+ eventId
511
+ );
512
+
513
+ const patientPromise = this.deletePatientCalendarEvent(patientId, eventId);
514
+
515
+ // Wait for all operations to complete
516
+ await Promise.all([clinicPromise, practitionerPromise, patientPromise]);
517
+ }
518
+
519
+ /**
520
+ * Gets pending appointments for a clinic
521
+ * @param clinicId - ID of the clinic
522
+ * @returns Array of pending calendar events
523
+ */
524
+ async getPendingClinicAppointments(
525
+ clinicId: string
526
+ ): Promise<CalendarEvent[]> {
527
+ const events = await getClinicCalendarEventsUtil(
528
+ this.db,
529
+ clinicId,
530
+ new Date(0), // From the beginning of time
531
+ new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // To one year from now
532
+ );
533
+
534
+ return events.filter(
535
+ (event: CalendarEvent) =>
536
+ event.status === CalendarEventStatus.PENDING &&
537
+ event.eventType === CalendarEventType.APPOINTMENT
538
+ );
539
+ }
540
+
541
+ /**
542
+ * Gets confirmed appointments for a practitioner
543
+ * @param practitionerId - ID of the practitioner
544
+ * @returns Array of confirmed calendar events
545
+ */
546
+ async getConfirmedPractitionerAppointments(
547
+ practitionerId: string
548
+ ): Promise<CalendarEvent[]> {
549
+ const events = await getPractitionerCalendarEventsUtil(
550
+ this.db,
551
+ practitionerId,
552
+ new Date(0), // From the beginning of time
553
+ new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // To one year from now
554
+ );
555
+
556
+ return events.filter(
557
+ (event: CalendarEvent) =>
558
+ event.status === CalendarEventStatus.CONFIRMED &&
559
+ event.eventType === CalendarEventType.APPOINTMENT
560
+ );
561
+ }
562
+
563
+ /**
564
+ * Gets upcoming appointments for a patient
565
+ * @param patientId - ID of the patient
566
+ * @returns Array of upcoming calendar events
567
+ */
568
+ async getUpcomingPatientAppointments(
569
+ patientId: string
570
+ ): Promise<CalendarEvent[]> {
571
+ const now = new Date();
572
+ const events = await getPatientCalendarEventsUtil(
573
+ this.db,
574
+ patientId,
575
+ now,
576
+ new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) // To one year from now
577
+ );
578
+
579
+ return events.filter(
580
+ (event: CalendarEvent) =>
581
+ event.eventType === CalendarEventType.APPOINTMENT
582
+ );
583
+ }
584
+
585
+ /**
586
+ * Confirms an appointment
587
+ * @param clinicId - ID of the clinic
588
+ * @param practitionerId - ID of the practitioner
589
+ * @param patientId - ID of the patient
590
+ * @param eventId - ID of the event
591
+ * @param syncWithExternalCalendars - Whether to sync with external calendars
592
+ * @returns Confirmed calendar event
593
+ */
594
+ async confirmAppointment(
595
+ clinicId: string,
596
+ practitionerId: string,
597
+ patientId: string,
598
+ eventId: string,
599
+ syncWithExternalCalendars: boolean = true
600
+ ): Promise<CalendarEvent> {
601
+ return this.updateAppointment(
602
+ clinicId,
603
+ practitionerId,
604
+ patientId,
605
+ eventId,
606
+ { status: CalendarEventStatus.CONFIRMED },
607
+ syncWithExternalCalendars
608
+ );
609
+ }
610
+
611
+ /**
612
+ * Rejects an appointment
613
+ * @param clinicId - ID of the clinic
614
+ * @param practitionerId - ID of the practitioner
615
+ * @param patientId - ID of the patient
616
+ * @param eventId - ID of the event
617
+ * @param syncWithExternalCalendars - Whether to sync with external calendars
618
+ * @returns Rejected calendar event
619
+ */
620
+ async rejectAppointment(
621
+ clinicId: string,
622
+ practitionerId: string,
623
+ patientId: string,
624
+ eventId: string,
625
+ syncWithExternalCalendars: boolean = true
626
+ ): Promise<CalendarEvent> {
627
+ return this.updateAppointment(
628
+ clinicId,
629
+ practitionerId,
630
+ patientId,
631
+ eventId,
632
+ { status: CalendarEventStatus.REJECTED },
633
+ syncWithExternalCalendars
634
+ );
635
+ }
636
+
637
+ /**
638
+ * Cancels an appointment
639
+ * @param clinicId - ID of the clinic
640
+ * @param practitionerId - ID of the practitioner
641
+ * @param patientId - ID of the patient
642
+ * @param eventId - ID of the event
643
+ * @param syncWithExternalCalendars - Whether to sync with external calendars
644
+ * @returns Canceled calendar event
645
+ */
646
+ async cancelAppointment(
647
+ clinicId: string,
648
+ practitionerId: string,
649
+ patientId: string,
650
+ eventId: string,
651
+ syncWithExternalCalendars: boolean = true
652
+ ): Promise<CalendarEvent> {
653
+ return this.updateAppointment(
654
+ clinicId,
655
+ practitionerId,
656
+ patientId,
657
+ eventId,
658
+ { status: CalendarEventStatus.CANCELED },
659
+ syncWithExternalCalendars
660
+ );
661
+ }
662
+
663
+ /**
664
+ * Reschedules an appointment
665
+ * @param clinicId - ID of the clinic
666
+ * @param practitionerId - ID of the practitioner
667
+ * @param patientId - ID of the patient
668
+ * @param eventId - ID of the event
669
+ * @param newEventTime - New event time
670
+ * @param syncWithExternalCalendars - Whether to sync with external calendars
671
+ * @returns Rescheduled calendar event
672
+ */
673
+ async rescheduleAppointment(
674
+ clinicId: string,
675
+ practitionerId: string,
676
+ patientId: string,
677
+ eventId: string,
678
+ newEventTime: CalendarEventTime,
679
+ syncWithExternalCalendars: boolean = true
680
+ ): Promise<CalendarEvent> {
681
+ // First get the current event to preserve any other fields
682
+ const currentEvent = await this.getClinicCalendarEvent(clinicId, eventId);
683
+
684
+ if (!currentEvent) {
685
+ throw new Error(`Appointment with ID ${eventId} not found`);
686
+ }
687
+
688
+ // Update the appointment with new time and status
689
+ return this.updateAppointment(
690
+ clinicId,
691
+ practitionerId,
692
+ patientId,
693
+ eventId,
694
+ {
695
+ status: CalendarEventStatus.RESCHEDULED,
696
+ eventTime: newEventTime,
697
+ },
698
+ syncWithExternalCalendars
699
+ );
700
+ }
701
+
702
+ /**
703
+ * Gets the SyncedCalendarsService for managing synced calendars
704
+ * @returns SyncedCalendarsService instance
705
+ */
706
+ getSyncedCalendarsService(): SyncedCalendarsService {
707
+ return this.syncedCalendarsService;
708
+ }
709
+
710
+ /**
711
+ * Syncs an appointment with external calendars
712
+ * @param clinicId - ID of the clinic
713
+ * @param practitionerId - ID of the practitioner
714
+ * @param patientId - ID of the patient
715
+ * @param eventId - ID of the event
716
+ * @returns Updated calendar event with sync information
717
+ */
718
+ async syncAppointmentWithExternalCalendars(
719
+ clinicId: string,
720
+ practitionerId: string,
721
+ patientId: string,
722
+ eventId: string
723
+ ): Promise<CalendarEvent> {
724
+ // Get the appointment
725
+ const appointment = await this.getClinicCalendarEvent(clinicId, eventId);
726
+
727
+ if (!appointment) {
728
+ throw new Error(`Appointment with ID ${eventId} not found`);
729
+ }
730
+
731
+ // Get synced calendars for all entities
732
+ const clinicCalendars =
733
+ await this.syncedCalendarsService.getClinicSyncedCalendars(clinicId);
734
+ const practitionerCalendars =
735
+ await this.syncedCalendarsService.getPractitionerSyncedCalendars(
736
+ practitionerId
737
+ );
738
+ const patientCalendars =
739
+ await this.syncedCalendarsService.getPatientSyncedCalendars(patientId);
740
+
741
+ // Combine all active calendars
742
+ const activeCalendars = [
743
+ ...clinicCalendars.filter((cal) => cal.isActive),
744
+ ...practitionerCalendars.filter((cal) => cal.isActive),
745
+ ...patientCalendars.filter((cal) => cal.isActive),
746
+ ];
747
+
748
+ // If no active calendars, return the appointment as is
749
+ if (activeCalendars.length === 0) {
750
+ return appointment;
751
+ }
752
+
753
+ // Array to store synced calendar event IDs
754
+ const syncedCalendarEvents: SyncedCalendarEvent[] = [];
755
+ const currentTime = Timestamp.now();
756
+
757
+ // Sync with each calendar
758
+ for (const calendar of activeCalendars) {
759
+ try {
760
+ // Sync based on provider
761
+ if (calendar.provider === SyncedCalendarProvider.GOOGLE) {
762
+ let syncResult;
763
+
764
+ // Determine which entity owns this calendar and sync accordingly
765
+ if (clinicCalendars.some((c) => c.id === calendar.id)) {
766
+ syncResult =
767
+ await this.syncedCalendarsService.syncClinicEventsToGoogleCalendar(
768
+ clinicId,
769
+ calendar.id,
770
+ [appointment]
771
+ );
772
+ } else if (practitionerCalendars.some((c) => c.id === calendar.id)) {
773
+ syncResult =
774
+ await this.syncedCalendarsService.syncPractitionerEventsToGoogleCalendar(
775
+ practitionerId,
776
+ calendar.id,
777
+ [appointment]
778
+ );
779
+ } else if (patientCalendars.some((c) => c.id === calendar.id)) {
780
+ syncResult =
781
+ await this.syncedCalendarsService.syncPatientEventsToGoogleCalendar(
782
+ patientId,
783
+ calendar.id,
784
+ [appointment]
785
+ );
786
+ }
787
+
788
+ // If sync was successful and we have event IDs, add them to our list
789
+ if (syncResult && syncResult.success && syncResult.syncedEvents > 0) {
790
+ // Get the external event ID - this might need to be adjusted based on the actual return structure
791
+ const externalEventId =
792
+ (syncResult as any).eventIds?.[0] || appointment.id;
793
+
794
+ syncedCalendarEvents.push({
795
+ eventId: externalEventId,
796
+ syncedCalendarProvider: SyncedCalendarProvider.GOOGLE,
797
+ syncedAt: currentTime,
798
+ });
799
+ }
800
+ }
801
+ // Add other providers as needed (Outlook, Apple, etc.)
802
+ } catch (error) {
803
+ console.error(`Error syncing with calendar ${calendar.id}:`, error);
804
+ // Continue with other calendars even if one fails
805
+ }
806
+ }
807
+
808
+ // Update the appointment with synced calendar information
809
+ if (syncedCalendarEvents.length > 0) {
810
+ return this.updateAppointment(
811
+ clinicId,
812
+ practitionerId,
813
+ patientId,
814
+ eventId,
815
+ {
816
+ syncedCalendarEventId: syncedCalendarEvents,
817
+ }
818
+ );
819
+ }
820
+
821
+ return appointment;
822
+ }
823
+
824
+ /**
825
+ * Imports events from external calendars
826
+ * @param entityType - Type of entity (practitioner, patient, clinic)
827
+ * @param entityId - ID of the entity
828
+ * @param startDate - Start date for fetching events
829
+ * @param endDate - End date for fetching events
830
+ * @returns Number of events imported
831
+ */
832
+ async importEventsFromExternalCalendars(
833
+ entityType: "practitioner" | "patient" | "clinic",
834
+ entityId: string,
835
+ startDate: Date,
836
+ endDate: Date
837
+ ): Promise<number> {
838
+ let syncedCalendars: any[] = [];
839
+
840
+ // Get synced calendars for the entity
841
+ switch (entityType) {
842
+ case "practitioner":
843
+ syncedCalendars =
844
+ await this.syncedCalendarsService.getPractitionerSyncedCalendars(
845
+ entityId
846
+ );
847
+ break;
848
+ case "patient":
849
+ syncedCalendars =
850
+ await this.syncedCalendarsService.getPatientSyncedCalendars(entityId);
851
+ break;
852
+ case "clinic":
853
+ syncedCalendars =
854
+ await this.syncedCalendarsService.getClinicSyncedCalendars(entityId);
855
+ break;
856
+ }
857
+
858
+ // Filter active calendars
859
+ const activeCalendars = syncedCalendars.filter((cal) => cal.isActive);
860
+
861
+ if (activeCalendars.length === 0) {
862
+ return 0;
863
+ }
864
+
865
+ let importedEventsCount = 0;
866
+ const currentTime = Timestamp.now();
867
+
868
+ // Import from each calendar
869
+ for (const calendar of activeCalendars) {
870
+ try {
871
+ let externalEvents: any[] = [];
872
+
873
+ // Fetch events based on provider and entity type
874
+ if (calendar.provider === SyncedCalendarProvider.GOOGLE) {
875
+ switch (entityType) {
876
+ case "practitioner":
877
+ externalEvents =
878
+ await this.syncedCalendarsService.fetchEventsFromPractitionerGoogleCalendar(
879
+ entityId,
880
+ calendar.id,
881
+ startDate,
882
+ endDate
883
+ );
884
+ break;
885
+ case "patient":
886
+ externalEvents =
887
+ await this.syncedCalendarsService.fetchEventsFromPatientGoogleCalendar(
888
+ entityId,
889
+ calendar.id,
890
+ startDate,
891
+ endDate
892
+ );
893
+ break;
894
+ case "clinic":
895
+ externalEvents =
896
+ await this.syncedCalendarsService.fetchEventsFromClinicGoogleCalendar(
897
+ entityId,
898
+ calendar.id,
899
+ startDate,
900
+ endDate
901
+ );
902
+ break;
903
+ }
904
+ }
905
+ // Add other providers as needed
906
+
907
+ // Process and import each event
908
+ for (const externalEvent of externalEvents) {
909
+ try {
910
+ // Create event data from external event
911
+ const eventData: Omit<
912
+ CreateCalendarEventData,
913
+ "id" | "createdAt" | "updatedAt"
914
+ > = {
915
+ eventName: externalEvent.summary || "Imported Event",
916
+ eventTime: {
917
+ start: Timestamp.fromDate(
918
+ new Date(
919
+ externalEvent.start.dateTime || externalEvent.start.date
920
+ )
921
+ ),
922
+ end: Timestamp.fromDate(
923
+ new Date(externalEvent.end.dateTime || externalEvent.end.date)
924
+ ),
925
+ },
926
+ description: externalEvent.description || "",
927
+ status: CalendarEventStatus.CONFIRMED,
928
+ // Set sync status to EXTERNAL as this is imported from an external calendar
929
+ syncStatus: CalendarSyncStatus.EXTERNAL,
930
+ eventType: CalendarEventType.BLOCKING, // Use BLOCKING type for external events
931
+ syncedCalendarEventId: [
932
+ {
933
+ eventId: externalEvent.id,
934
+ syncedCalendarProvider: calendar.provider,
935
+ syncedAt: currentTime,
936
+ },
937
+ ],
938
+ };
939
+
940
+ // Create the event in our system
941
+ switch (entityType) {
942
+ case "practitioner":
943
+ await this.createPractitionerCalendarEvent(entityId, eventData);
944
+ break;
945
+ case "patient":
946
+ await this.createPatientCalendarEvent(entityId, eventData);
947
+ break;
948
+ case "clinic":
949
+ await this.createClinicCalendarEvent(entityId, eventData);
950
+ break;
951
+ }
952
+
953
+ importedEventsCount++;
954
+ } catch (eventError) {
955
+ console.error("Error importing event:", eventError);
956
+ // Continue with other events even if one fails
957
+ }
958
+ }
959
+ } catch (calendarError) {
960
+ console.error(
961
+ `Error fetching events from calendar ${calendar.id}:`,
962
+ calendarError
963
+ );
964
+ // Continue with other calendars even if one fails
965
+ }
966
+ }
967
+
968
+ return importedEventsCount;
969
+ }
970
+
971
+ /**
972
+ * Deletes an event from external calendars
973
+ * @param event - Calendar event to delete
974
+ * @param entityType - Type of entity (practitioner, patient, clinic)
975
+ * @param entityId - ID of the entity
976
+ * @returns Success status
977
+ */
978
+ async deleteEventFromExternalCalendars(
979
+ event: CalendarEvent,
980
+ entityType: "practitioner" | "patient" | "clinic",
981
+ entityId: string
982
+ ): Promise<boolean> {
983
+ // If the event doesn't have synced calendar event IDs, return success
984
+ if (
985
+ !event.syncedCalendarEventId ||
986
+ event.syncedCalendarEventId.length === 0
987
+ ) {
988
+ return true;
989
+ }
990
+
991
+ // Get synced calendars for the entity
992
+ let syncedCalendars: any[] = [];
993
+ switch (entityType) {
994
+ case "practitioner":
995
+ syncedCalendars =
996
+ await this.syncedCalendarsService.getPractitionerSyncedCalendars(
997
+ entityId
998
+ );
999
+ break;
1000
+ case "patient":
1001
+ syncedCalendars =
1002
+ await this.syncedCalendarsService.getPatientSyncedCalendars(entityId);
1003
+ break;
1004
+ case "clinic":
1005
+ syncedCalendars =
1006
+ await this.syncedCalendarsService.getClinicSyncedCalendars(entityId);
1007
+ break;
1008
+ }
1009
+
1010
+ // Filter active calendars
1011
+ const activeCalendars = syncedCalendars.filter((cal) => cal.isActive);
1012
+
1013
+ if (activeCalendars.length === 0) {
1014
+ return true;
1015
+ }
1016
+
1017
+ let allSuccessful = true;
1018
+
1019
+ // Delete from each external calendar
1020
+ for (const syncedEvent of event.syncedCalendarEventId) {
1021
+ try {
1022
+ // Find the calendar for this provider
1023
+ const calendar = activeCalendars.find(
1024
+ (cal) => cal.provider === syncedEvent.syncedCalendarProvider
1025
+ );
1026
+
1027
+ if (!calendar) {
1028
+ continue;
1029
+ }
1030
+
1031
+ // Delete based on provider and entity type
1032
+ if (
1033
+ syncedEvent.syncedCalendarProvider === SyncedCalendarProvider.GOOGLE
1034
+ ) {
1035
+ let success = false;
1036
+
1037
+ switch (entityType) {
1038
+ case "practitioner":
1039
+ success =
1040
+ await this.syncedCalendarsService.deletePractitionerGoogleCalendarEvent(
1041
+ entityId,
1042
+ calendar.id,
1043
+ syncedEvent.eventId
1044
+ );
1045
+ break;
1046
+ case "patient":
1047
+ success =
1048
+ await this.syncedCalendarsService.deletePatientGoogleCalendarEvent(
1049
+ entityId,
1050
+ calendar.id,
1051
+ syncedEvent.eventId
1052
+ );
1053
+ break;
1054
+ case "clinic":
1055
+ success =
1056
+ await this.syncedCalendarsService.deleteClinicGoogleCalendarEvent(
1057
+ entityId,
1058
+ calendar.id,
1059
+ syncedEvent.eventId
1060
+ );
1061
+ break;
1062
+ }
1063
+
1064
+ if (!success) {
1065
+ allSuccessful = false;
1066
+ }
1067
+ }
1068
+ // Add other providers as needed
1069
+ } catch (error) {
1070
+ console.error(`Error deleting event from external calendar:`, error);
1071
+ allSuccessful = false;
1072
+ }
1073
+ }
1074
+
1075
+ return allSuccessful;
1076
+ }
1077
+ }