@blackcode_sa/metaestetics-api 1.12.62 → 1.12.63

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 (273) hide show
  1. package/dist/admin/index.d.mts +4 -2
  2. package/dist/admin/index.d.ts +4 -2
  3. package/dist/admin/index.js +4 -45
  4. package/dist/admin/index.mjs +4 -45
  5. package/dist/backoffice/index.d.mts +9 -0
  6. package/dist/backoffice/index.d.ts +9 -0
  7. package/dist/backoffice/index.js +11 -0
  8. package/dist/backoffice/index.mjs +11 -0
  9. package/dist/index.d.mts +99 -3
  10. package/dist/index.d.ts +99 -3
  11. package/dist/index.js +545 -281
  12. package/dist/index.mjs +867 -603
  13. package/package.json +119 -119
  14. package/src/__mocks__/firstore.ts +10 -10
  15. package/src/admin/aggregation/README.md +79 -79
  16. package/src/admin/aggregation/appointment/README.md +128 -128
  17. package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +1844 -1844
  18. package/src/admin/aggregation/appointment/index.ts +1 -1
  19. package/src/admin/aggregation/clinic/README.md +52 -52
  20. package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +703 -703
  21. package/src/admin/aggregation/clinic/index.ts +1 -1
  22. package/src/admin/aggregation/forms/README.md +13 -13
  23. package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
  24. package/src/admin/aggregation/forms/index.ts +1 -1
  25. package/src/admin/aggregation/index.ts +8 -8
  26. package/src/admin/aggregation/patient/README.md +27 -27
  27. package/src/admin/aggregation/patient/index.ts +1 -1
  28. package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
  29. package/src/admin/aggregation/practitioner/README.md +42 -42
  30. package/src/admin/aggregation/practitioner/index.ts +1 -1
  31. package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
  32. package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
  33. package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
  34. package/src/admin/aggregation/procedure/README.md +43 -43
  35. package/src/admin/aggregation/procedure/index.ts +1 -1
  36. package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
  37. package/src/admin/aggregation/reviews/index.ts +1 -1
  38. package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +641 -689
  39. package/src/admin/booking/README.md +125 -125
  40. package/src/admin/booking/booking.admin.ts +1037 -1037
  41. package/src/admin/booking/booking.calculator.ts +712 -712
  42. package/src/admin/booking/booking.types.ts +59 -59
  43. package/src/admin/booking/index.ts +3 -3
  44. package/src/admin/booking/timezones-problem.md +185 -185
  45. package/src/admin/calendar/README.md +7 -7
  46. package/src/admin/calendar/calendar.admin.service.ts +345 -345
  47. package/src/admin/calendar/index.ts +1 -1
  48. package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
  49. package/src/admin/documentation-templates/index.ts +1 -1
  50. package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
  51. package/src/admin/free-consultation/index.ts +1 -1
  52. package/src/admin/index.ts +75 -75
  53. package/src/admin/logger/index.ts +78 -78
  54. package/src/admin/mailing/README.md +95 -95
  55. package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
  56. package/src/admin/mailing/appointment/index.ts +1 -1
  57. package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
  58. package/src/admin/mailing/base.mailing.service.ts +208 -208
  59. package/src/admin/mailing/index.ts +3 -3
  60. package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
  61. package/src/admin/mailing/practitionerInvite/index.ts +2 -2
  62. package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
  63. package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
  64. package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
  65. package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
  66. package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
  67. package/src/admin/notifications/index.ts +1 -1
  68. package/src/admin/notifications/notifications.admin.ts +710 -710
  69. package/src/admin/requirements/README.md +128 -128
  70. package/src/admin/requirements/index.ts +1 -1
  71. package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
  72. package/src/admin/users/index.ts +1 -1
  73. package/src/admin/users/user-profile.admin.ts +405 -405
  74. package/src/backoffice/constants/certification.constants.ts +13 -13
  75. package/src/backoffice/constants/index.ts +1 -1
  76. package/src/backoffice/errors/backoffice.errors.ts +181 -181
  77. package/src/backoffice/errors/index.ts +1 -1
  78. package/src/backoffice/expo-safe/README.md +26 -26
  79. package/src/backoffice/expo-safe/index.ts +41 -41
  80. package/src/backoffice/index.ts +5 -5
  81. package/src/backoffice/services/FIXES_README.md +102 -102
  82. package/src/backoffice/services/README.md +40 -40
  83. package/src/backoffice/services/brand.service.ts +256 -256
  84. package/src/backoffice/services/category.service.ts +318 -318
  85. package/src/backoffice/services/constants.service.ts +385 -385
  86. package/src/backoffice/services/documentation-template.service.ts +202 -202
  87. package/src/backoffice/services/index.ts +8 -8
  88. package/src/backoffice/services/migrate-products.ts +116 -116
  89. package/src/backoffice/services/product.service.ts +553 -553
  90. package/src/backoffice/services/requirement.service.ts +235 -235
  91. package/src/backoffice/services/subcategory.service.ts +395 -395
  92. package/src/backoffice/services/technology.service.ts +1083 -1070
  93. package/src/backoffice/types/README.md +12 -12
  94. package/src/backoffice/types/admin-constants.types.ts +69 -69
  95. package/src/backoffice/types/brand.types.ts +29 -29
  96. package/src/backoffice/types/category.types.ts +62 -62
  97. package/src/backoffice/types/documentation-templates.types.ts +28 -28
  98. package/src/backoffice/types/index.ts +10 -10
  99. package/src/backoffice/types/procedure-product.types.ts +38 -38
  100. package/src/backoffice/types/product.types.ts +240 -240
  101. package/src/backoffice/types/requirement.types.ts +63 -63
  102. package/src/backoffice/types/static/README.md +18 -18
  103. package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
  104. package/src/backoffice/types/static/certification.types.ts +37 -37
  105. package/src/backoffice/types/static/contraindication.types.ts +19 -19
  106. package/src/backoffice/types/static/index.ts +6 -6
  107. package/src/backoffice/types/static/pricing.types.ts +16 -16
  108. package/src/backoffice/types/static/procedure-family.types.ts +14 -14
  109. package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
  110. package/src/backoffice/types/subcategory.types.ts +34 -34
  111. package/src/backoffice/types/technology.types.ts +163 -161
  112. package/src/backoffice/validations/index.ts +1 -1
  113. package/src/backoffice/validations/schemas.ts +164 -163
  114. package/src/config/__mocks__/firebase.ts +99 -99
  115. package/src/config/firebase.ts +78 -78
  116. package/src/config/index.ts +9 -9
  117. package/src/errors/auth.error.ts +6 -6
  118. package/src/errors/auth.errors.ts +200 -200
  119. package/src/errors/clinic.errors.ts +32 -32
  120. package/src/errors/firebase.errors.ts +47 -47
  121. package/src/errors/user.errors.ts +99 -99
  122. package/src/index.backup.ts +407 -407
  123. package/src/index.ts +6 -6
  124. package/src/locales/en.ts +31 -31
  125. package/src/recommender/admin/index.ts +1 -1
  126. package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
  127. package/src/recommender/front/index.ts +1 -1
  128. package/src/recommender/front/services/onboarding.service.ts +5 -5
  129. package/src/recommender/front/services/recommender.service.ts +3 -3
  130. package/src/recommender/index.ts +1 -1
  131. package/src/services/PATIENTAUTH.MD +197 -197
  132. package/src/services/README.md +106 -106
  133. package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
  134. package/src/services/__tests__/auth/auth.setup.ts +293 -293
  135. package/src/services/__tests__/auth.service.test.ts +346 -346
  136. package/src/services/__tests__/base.service.test.ts +77 -77
  137. package/src/services/__tests__/user.service.test.ts +528 -528
  138. package/src/services/appointment/README.md +17 -17
  139. package/src/services/appointment/appointment.service.ts +2505 -2082
  140. package/src/services/appointment/index.ts +1 -1
  141. package/src/services/appointment/utils/appointment.utils.ts +552 -552
  142. package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
  143. package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
  144. package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
  145. package/src/services/appointment/utils/zone-management.utils.ts +353 -353
  146. package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
  147. package/src/services/auth/auth.service.ts +989 -989
  148. package/src/services/auth/auth.v2.service.ts +961 -961
  149. package/src/services/auth/index.ts +7 -7
  150. package/src/services/auth/utils/error.utils.ts +90 -90
  151. package/src/services/auth/utils/firebase.utils.ts +49 -49
  152. package/src/services/auth/utils/index.ts +21 -21
  153. package/src/services/auth/utils/practitioner.utils.ts +125 -125
  154. package/src/services/base.service.ts +41 -41
  155. package/src/services/calendar/calendar.service.ts +1077 -1077
  156. package/src/services/calendar/calendar.v2.service.ts +1683 -1683
  157. package/src/services/calendar/calendar.v3.service.ts +313 -313
  158. package/src/services/calendar/externalCalendar.service.ts +178 -178
  159. package/src/services/calendar/index.ts +5 -5
  160. package/src/services/calendar/synced-calendars.service.ts +743 -743
  161. package/src/services/calendar/utils/appointment.utils.ts +265 -265
  162. package/src/services/calendar/utils/calendar-event.utils.ts +646 -646
  163. package/src/services/calendar/utils/clinic.utils.ts +237 -237
  164. package/src/services/calendar/utils/docs.utils.ts +157 -157
  165. package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
  166. package/src/services/calendar/utils/index.ts +8 -8
  167. package/src/services/calendar/utils/patient.utils.ts +198 -198
  168. package/src/services/calendar/utils/practitioner.utils.ts +221 -221
  169. package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
  170. package/src/services/clinic/README.md +204 -204
  171. package/src/services/clinic/__tests__/clinic-admin.service.test.ts +287 -287
  172. package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
  173. package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
  174. package/src/services/clinic/billing-transactions.service.ts +217 -217
  175. package/src/services/clinic/clinic-admin.service.ts +202 -202
  176. package/src/services/clinic/clinic-group.service.ts +310 -310
  177. package/src/services/clinic/clinic.service.ts +708 -708
  178. package/src/services/clinic/index.ts +5 -5
  179. package/src/services/clinic/practitioner-invite.service.ts +519 -519
  180. package/src/services/clinic/utils/admin.utils.ts +551 -551
  181. package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
  182. package/src/services/clinic/utils/clinic.utils.ts +949 -949
  183. package/src/services/clinic/utils/filter.utils.d.ts +23 -23
  184. package/src/services/clinic/utils/filter.utils.ts +446 -446
  185. package/src/services/clinic/utils/index.ts +11 -11
  186. package/src/services/clinic/utils/photos.utils.ts +188 -188
  187. package/src/services/clinic/utils/search.utils.ts +84 -84
  188. package/src/services/clinic/utils/tag.utils.ts +124 -124
  189. package/src/services/documentation-templates/documentation-template.service.ts +537 -537
  190. package/src/services/documentation-templates/filled-document.service.ts +587 -587
  191. package/src/services/documentation-templates/index.ts +2 -2
  192. package/src/services/index.ts +13 -13
  193. package/src/services/media/index.ts +1 -1
  194. package/src/services/media/media.service.ts +418 -418
  195. package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
  196. package/src/services/notifications/index.ts +1 -1
  197. package/src/services/notifications/notification.service.ts +215 -215
  198. package/src/services/patient/README.md +48 -48
  199. package/src/services/patient/To-Do.md +43 -43
  200. package/src/services/patient/__tests__/patient.service.test.ts +294 -294
  201. package/src/services/patient/index.ts +2 -2
  202. package/src/services/patient/patient.service.ts +883 -883
  203. package/src/services/patient/patientRequirements.service.ts +285 -285
  204. package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
  205. package/src/services/patient/utils/clinic.utils.ts +80 -80
  206. package/src/services/patient/utils/docs.utils.ts +142 -142
  207. package/src/services/patient/utils/index.ts +9 -9
  208. package/src/services/patient/utils/location.utils.ts +126 -126
  209. package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
  210. package/src/services/patient/utils/medical.utils.ts +458 -458
  211. package/src/services/patient/utils/practitioner.utils.ts +260 -260
  212. package/src/services/patient/utils/profile.utils.ts +510 -510
  213. package/src/services/patient/utils/sensitive.utils.ts +260 -260
  214. package/src/services/patient/utils/token.utils.ts +211 -211
  215. package/src/services/practitioner/README.md +145 -145
  216. package/src/services/practitioner/index.ts +1 -1
  217. package/src/services/practitioner/practitioner.service.ts +1742 -1742
  218. package/src/services/procedure/README.md +163 -163
  219. package/src/services/procedure/index.ts +1 -1
  220. package/src/services/procedure/procedure.service.ts +1682 -1682
  221. package/src/services/reviews/index.ts +1 -1
  222. package/src/services/reviews/reviews.service.ts +636 -683
  223. package/src/services/user/index.ts +1 -1
  224. package/src/services/user/user.service.ts +489 -489
  225. package/src/services/user/user.v2.service.ts +466 -466
  226. package/src/types/appointment/index.ts +481 -453
  227. package/src/types/calendar/index.ts +258 -258
  228. package/src/types/calendar/synced-calendar.types.ts +66 -66
  229. package/src/types/clinic/index.ts +489 -489
  230. package/src/types/clinic/practitioner-invite.types.ts +91 -91
  231. package/src/types/clinic/preferences.types.ts +159 -159
  232. package/src/types/clinic/to-do +3 -3
  233. package/src/types/documentation-templates/index.ts +308 -308
  234. package/src/types/index.ts +44 -44
  235. package/src/types/notifications/README.md +77 -77
  236. package/src/types/notifications/index.ts +265 -265
  237. package/src/types/patient/aesthetic-analysis.types.ts +66 -66
  238. package/src/types/patient/allergies.ts +58 -58
  239. package/src/types/patient/index.ts +275 -273
  240. package/src/types/patient/medical-info.types.ts +152 -152
  241. package/src/types/patient/patient-requirements.ts +92 -92
  242. package/src/types/patient/token.types.ts +61 -61
  243. package/src/types/practitioner/index.ts +206 -206
  244. package/src/types/procedure/index.ts +181 -181
  245. package/src/types/profile/index.ts +39 -39
  246. package/src/types/reviews/index.ts +130 -132
  247. package/src/types/tz-lookup.d.ts +4 -4
  248. package/src/types/user/index.ts +38 -38
  249. package/src/utils/TIMESTAMPS.md +176 -176
  250. package/src/utils/TimestampUtils.ts +241 -241
  251. package/src/utils/index.ts +1 -1
  252. package/src/validations/appointment.schema.ts +574 -574
  253. package/src/validations/calendar.schema.ts +225 -225
  254. package/src/validations/clinic.schema.ts +493 -493
  255. package/src/validations/common.schema.ts +25 -25
  256. package/src/validations/documentation-templates/index.ts +1 -1
  257. package/src/validations/documentation-templates/template.schema.ts +220 -220
  258. package/src/validations/documentation-templates.schema.ts +10 -10
  259. package/src/validations/index.ts +20 -20
  260. package/src/validations/media.schema.ts +10 -10
  261. package/src/validations/notification.schema.ts +90 -90
  262. package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
  263. package/src/validations/patient/medical-info.schema.ts +125 -125
  264. package/src/validations/patient/patient-requirements.schema.ts +84 -84
  265. package/src/validations/patient/token.schema.ts +29 -29
  266. package/src/validations/patient.schema.ts +217 -216
  267. package/src/validations/practitioner.schema.ts +222 -222
  268. package/src/validations/procedure-product.schema.ts +41 -41
  269. package/src/validations/procedure.schema.ts +124 -124
  270. package/src/validations/profile-info.schema.ts +41 -41
  271. package/src/validations/reviews.schema.ts +189 -195
  272. package/src/validations/schemas.ts +104 -104
  273. 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
+ }