@blackcode_sa/metaestetics-api 1.13.3 → 1.13.5

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