@blackcode_sa/metaestetics-api 1.13.5 → 1.13.8

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 (295) hide show
  1. package/dist/admin/index.d.mts +20 -1
  2. package/dist/admin/index.d.ts +20 -1
  3. package/dist/admin/index.js +217 -1
  4. package/dist/admin/index.mjs +217 -1
  5. package/dist/index.d.mts +26 -3
  6. package/dist/index.d.ts +26 -3
  7. package/dist/index.js +168 -6
  8. package/dist/index.mjs +168 -6
  9. package/package.json +121 -121
  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 +1984 -1984
  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 +966 -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 -689
  35. package/src/admin/analytics/analytics.admin.service.ts +278 -278
  36. package/src/admin/analytics/index.ts +2 -2
  37. package/src/admin/booking/README.md +125 -125
  38. package/src/admin/booking/booking.admin.ts +1037 -1037
  39. package/src/admin/booking/booking.calculator.ts +712 -712
  40. package/src/admin/booking/booking.types.ts +59 -59
  41. package/src/admin/booking/index.ts +3 -3
  42. package/src/admin/booking/timezones-problem.md +185 -185
  43. package/src/admin/calendar/README.md +7 -7
  44. package/src/admin/calendar/calendar.admin.service.ts +345 -345
  45. package/src/admin/calendar/index.ts +1 -1
  46. package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
  47. package/src/admin/documentation-templates/index.ts +1 -1
  48. package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
  49. package/src/admin/free-consultation/index.ts +1 -1
  50. package/src/admin/index.ts +81 -81
  51. package/src/admin/logger/index.ts +78 -78
  52. package/src/admin/mailing/README.md +95 -95
  53. package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
  54. package/src/admin/mailing/appointment/index.ts +1 -1
  55. package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
  56. package/src/admin/mailing/base.mailing.service.ts +208 -208
  57. package/src/admin/mailing/index.ts +3 -3
  58. package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
  59. package/src/admin/mailing/practitionerInvite/index.ts +2 -2
  60. package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
  61. package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
  62. package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
  63. package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
  64. package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
  65. package/src/admin/notifications/index.ts +1 -1
  66. package/src/admin/notifications/notifications.admin.ts +710 -710
  67. package/src/admin/requirements/README.md +128 -128
  68. package/src/admin/requirements/index.ts +1 -1
  69. package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
  70. package/src/admin/users/index.ts +1 -1
  71. package/src/admin/users/user-profile.admin.ts +405 -405
  72. package/src/backoffice/constants/certification.constants.ts +13 -13
  73. package/src/backoffice/constants/index.ts +1 -1
  74. package/src/backoffice/errors/backoffice.errors.ts +181 -181
  75. package/src/backoffice/errors/index.ts +1 -1
  76. package/src/backoffice/expo-safe/README.md +26 -26
  77. package/src/backoffice/expo-safe/index.ts +41 -41
  78. package/src/backoffice/index.ts +5 -5
  79. package/src/backoffice/services/FIXES_README.md +102 -102
  80. package/src/backoffice/services/README.md +57 -57
  81. package/src/backoffice/services/analytics.service.proposal.md +863 -863
  82. package/src/backoffice/services/analytics.service.summary.md +143 -143
  83. package/src/backoffice/services/brand.service.ts +256 -256
  84. package/src/backoffice/services/category.service.ts +384 -384
  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 +10 -10
  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 +461 -461
  92. package/src/backoffice/services/technology.service.ts +1151 -1151
  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 +67 -67
  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 +168 -168
  112. package/src/backoffice/validations/index.ts +1 -1
  113. package/src/backoffice/validations/schemas.ts +164 -164
  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 +211 -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/analytics/ARCHITECTURE.md +199 -199
  139. package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -225
  140. package/src/services/analytics/GROUPED_ANALYTICS.md +501 -501
  141. package/src/services/analytics/QUICK_START.md +393 -393
  142. package/src/services/analytics/README.md +304 -304
  143. package/src/services/analytics/SUMMARY.md +141 -141
  144. package/src/services/analytics/TRENDS.md +380 -380
  145. package/src/services/analytics/USAGE_GUIDE.md +518 -518
  146. package/src/services/analytics/analytics-cloud.service.ts +222 -222
  147. package/src/services/analytics/analytics.service.ts +2142 -2142
  148. package/src/services/analytics/index.ts +4 -4
  149. package/src/services/analytics/review-analytics.service.ts +941 -941
  150. package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -138
  151. package/src/services/analytics/utils/cost-calculation.utils.ts +182 -182
  152. package/src/services/analytics/utils/grouping.utils.ts +434 -434
  153. package/src/services/analytics/utils/stored-analytics.utils.ts +347 -347
  154. package/src/services/analytics/utils/time-calculation.utils.ts +186 -186
  155. package/src/services/analytics/utils/trend-calculation.utils.ts +200 -200
  156. package/src/services/appointment/README.md +17 -17
  157. package/src/services/appointment/appointment.service.ts +2558 -2558
  158. package/src/services/appointment/index.ts +1 -1
  159. package/src/services/appointment/utils/appointment.utils.ts +552 -552
  160. package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
  161. package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
  162. package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
  163. package/src/services/appointment/utils/zone-management.utils.ts +353 -353
  164. package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
  165. package/src/services/auth/auth.service.ts +1043 -989
  166. package/src/services/auth/auth.v2.service.ts +961 -961
  167. package/src/services/auth/index.ts +7 -7
  168. package/src/services/auth/utils/error.utils.ts +90 -90
  169. package/src/services/auth/utils/firebase.utils.ts +49 -49
  170. package/src/services/auth/utils/index.ts +21 -21
  171. package/src/services/auth/utils/practitioner.utils.ts +125 -125
  172. package/src/services/base.service.ts +41 -41
  173. package/src/services/calendar/calendar.service.ts +1077 -1077
  174. package/src/services/calendar/calendar.v2.service.ts +1683 -1683
  175. package/src/services/calendar/calendar.v3.service.ts +313 -313
  176. package/src/services/calendar/externalCalendar.service.ts +178 -178
  177. package/src/services/calendar/index.ts +5 -5
  178. package/src/services/calendar/synced-calendars.service.ts +743 -743
  179. package/src/services/calendar/utils/appointment.utils.ts +265 -265
  180. package/src/services/calendar/utils/calendar-event.utils.ts +646 -646
  181. package/src/services/calendar/utils/clinic.utils.ts +237 -237
  182. package/src/services/calendar/utils/docs.utils.ts +157 -157
  183. package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
  184. package/src/services/calendar/utils/index.ts +8 -8
  185. package/src/services/calendar/utils/patient.utils.ts +198 -198
  186. package/src/services/calendar/utils/practitioner.utils.ts +221 -221
  187. package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
  188. package/src/services/clinic/README.md +204 -204
  189. package/src/services/clinic/__tests__/clinic-admin.service.test.ts +287 -287
  190. package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
  191. package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
  192. package/src/services/clinic/billing-transactions.service.ts +217 -217
  193. package/src/services/clinic/clinic-admin.service.ts +202 -202
  194. package/src/services/clinic/clinic-group.service.ts +310 -310
  195. package/src/services/clinic/clinic.service.ts +708 -708
  196. package/src/services/clinic/index.ts +5 -5
  197. package/src/services/clinic/practitioner-invite.service.ts +519 -519
  198. package/src/services/clinic/utils/admin.utils.ts +551 -551
  199. package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
  200. package/src/services/clinic/utils/clinic.utils.ts +949 -949
  201. package/src/services/clinic/utils/filter.utils.d.ts +23 -23
  202. package/src/services/clinic/utils/filter.utils.ts +446 -446
  203. package/src/services/clinic/utils/index.ts +11 -11
  204. package/src/services/clinic/utils/photos.utils.ts +188 -188
  205. package/src/services/clinic/utils/search.utils.ts +84 -84
  206. package/src/services/clinic/utils/tag.utils.ts +124 -124
  207. package/src/services/documentation-templates/documentation-template.service.ts +537 -537
  208. package/src/services/documentation-templates/filled-document.service.ts +587 -587
  209. package/src/services/documentation-templates/index.ts +2 -2
  210. package/src/services/index.ts +14 -14
  211. package/src/services/media/index.ts +1 -1
  212. package/src/services/media/media.service.ts +418 -418
  213. package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
  214. package/src/services/notifications/index.ts +1 -1
  215. package/src/services/notifications/notification.service.ts +215 -215
  216. package/src/services/patient/README.md +48 -48
  217. package/src/services/patient/To-Do.md +43 -43
  218. package/src/services/patient/__tests__/patient.service.test.ts +294 -294
  219. package/src/services/patient/index.ts +2 -2
  220. package/src/services/patient/patient.service.ts +883 -883
  221. package/src/services/patient/patientRequirements.service.ts +285 -285
  222. package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
  223. package/src/services/patient/utils/clinic.utils.ts +80 -80
  224. package/src/services/patient/utils/docs.utils.ts +142 -142
  225. package/src/services/patient/utils/index.ts +9 -9
  226. package/src/services/patient/utils/location.utils.ts +126 -126
  227. package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
  228. package/src/services/patient/utils/medical.utils.ts +458 -458
  229. package/src/services/patient/utils/practitioner.utils.ts +260 -260
  230. package/src/services/patient/utils/profile.utils.ts +510 -510
  231. package/src/services/patient/utils/sensitive.utils.ts +260 -260
  232. package/src/services/patient/utils/token.utils.ts +211 -211
  233. package/src/services/practitioner/README.md +145 -145
  234. package/src/services/practitioner/index.ts +1 -1
  235. package/src/services/practitioner/practitioner.service.ts +1799 -1742
  236. package/src/services/procedure/README.md +163 -163
  237. package/src/services/procedure/index.ts +1 -1
  238. package/src/services/procedure/procedure.service.ts +2307 -2200
  239. package/src/services/reviews/index.ts +1 -1
  240. package/src/services/reviews/reviews.service.ts +734 -734
  241. package/src/services/user/index.ts +1 -1
  242. package/src/services/user/user.service.ts +489 -489
  243. package/src/services/user/user.v2.service.ts +466 -466
  244. package/src/types/analytics/analytics.types.ts +597 -597
  245. package/src/types/analytics/grouped-analytics.types.ts +173 -173
  246. package/src/types/analytics/index.ts +4 -4
  247. package/src/types/analytics/stored-analytics.types.ts +137 -137
  248. package/src/types/appointment/index.ts +480 -480
  249. package/src/types/calendar/index.ts +258 -258
  250. package/src/types/calendar/synced-calendar.types.ts +66 -66
  251. package/src/types/clinic/index.ts +498 -498
  252. package/src/types/clinic/practitioner-invite.types.ts +91 -91
  253. package/src/types/clinic/preferences.types.ts +159 -159
  254. package/src/types/clinic/to-do +3 -3
  255. package/src/types/documentation-templates/index.ts +308 -308
  256. package/src/types/index.ts +47 -47
  257. package/src/types/notifications/README.md +77 -77
  258. package/src/types/notifications/index.ts +286 -286
  259. package/src/types/patient/aesthetic-analysis.types.ts +66 -66
  260. package/src/types/patient/allergies.ts +58 -58
  261. package/src/types/patient/index.ts +275 -275
  262. package/src/types/patient/medical-info.types.ts +152 -152
  263. package/src/types/patient/patient-requirements.ts +92 -92
  264. package/src/types/patient/token.types.ts +61 -61
  265. package/src/types/practitioner/index.ts +206 -206
  266. package/src/types/procedure/index.ts +181 -181
  267. package/src/types/profile/index.ts +39 -39
  268. package/src/types/reviews/index.ts +132 -132
  269. package/src/types/tz-lookup.d.ts +4 -4
  270. package/src/types/user/index.ts +38 -38
  271. package/src/utils/TIMESTAMPS.md +176 -176
  272. package/src/utils/TimestampUtils.ts +241 -241
  273. package/src/utils/index.ts +1 -1
  274. package/src/validations/appointment.schema.ts +574 -574
  275. package/src/validations/calendar.schema.ts +225 -225
  276. package/src/validations/clinic.schema.ts +494 -494
  277. package/src/validations/common.schema.ts +25 -25
  278. package/src/validations/documentation-templates/index.ts +1 -1
  279. package/src/validations/documentation-templates/template.schema.ts +220 -220
  280. package/src/validations/documentation-templates.schema.ts +10 -10
  281. package/src/validations/index.ts +20 -20
  282. package/src/validations/media.schema.ts +10 -10
  283. package/src/validations/notification.schema.ts +90 -90
  284. package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
  285. package/src/validations/patient/medical-info.schema.ts +125 -125
  286. package/src/validations/patient/patient-requirements.schema.ts +84 -84
  287. package/src/validations/patient/token.schema.ts +29 -29
  288. package/src/validations/patient.schema.ts +217 -217
  289. package/src/validations/practitioner.schema.ts +222 -222
  290. package/src/validations/procedure-product.schema.ts +41 -41
  291. package/src/validations/procedure.schema.ts +124 -124
  292. package/src/validations/profile-info.schema.ts +41 -41
  293. package/src/validations/reviews.schema.ts +195 -195
  294. package/src/validations/schemas.ts +104 -104
  295. 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
+ }