@blackcode_sa/metaestetics-api 1.15.16 → 1.15.17-staging.0

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 (326) hide show
  1. package/dist/admin/index.d.mts +377 -222
  2. package/dist/admin/index.d.ts +377 -222
  3. package/dist/admin/index.js +625 -206
  4. package/dist/admin/index.mjs +624 -206
  5. package/dist/backoffice/index.d.mts +24 -0
  6. package/dist/backoffice/index.d.ts +24 -0
  7. package/dist/index.d.mts +292 -4
  8. package/dist/index.d.ts +292 -4
  9. package/dist/index.js +1142 -630
  10. package/dist/index.mjs +1137 -617
  11. package/package.json +2 -1
  12. package/src/__mocks__/firstore.ts +10 -10
  13. package/src/admin/aggregation/README.md +79 -79
  14. package/src/admin/aggregation/appointment/README.md +151 -129
  15. package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +2137 -2091
  16. package/src/admin/aggregation/appointment/index.ts +1 -1
  17. package/src/admin/aggregation/clinic/README.md +52 -52
  18. package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +966 -966
  19. package/src/admin/aggregation/clinic/index.ts +1 -1
  20. package/src/admin/aggregation/forms/README.md +13 -13
  21. package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
  22. package/src/admin/aggregation/forms/index.ts +1 -1
  23. package/src/admin/aggregation/index.ts +8 -8
  24. package/src/admin/aggregation/patient/README.md +27 -27
  25. package/src/admin/aggregation/patient/index.ts +1 -1
  26. package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
  27. package/src/admin/aggregation/practitioner/README.md +42 -42
  28. package/src/admin/aggregation/practitioner/index.ts +1 -1
  29. package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
  30. package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
  31. package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
  32. package/src/admin/aggregation/procedure/README.md +43 -43
  33. package/src/admin/aggregation/procedure/index.ts +1 -1
  34. package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
  35. package/src/admin/aggregation/reviews/index.ts +1 -1
  36. package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +689 -689
  37. package/src/admin/analytics/analytics.admin.service.ts +278 -278
  38. package/src/admin/analytics/index.ts +2 -2
  39. package/src/admin/booking/README.md +184 -125
  40. package/src/admin/booking/booking.admin.ts +1330 -1073
  41. package/src/admin/booking/booking.calculator.ts +850 -712
  42. package/src/admin/booking/booking.types.ts +76 -59
  43. package/src/admin/booking/index.ts +3 -3
  44. package/src/admin/booking/timezones-problem.md +185 -185
  45. package/src/admin/calendar/README.md +62 -7
  46. package/src/admin/calendar/calendar.admin.service.ts +345 -345
  47. package/src/admin/calendar/index.ts +2 -1
  48. package/src/admin/calendar/resource-calendar.admin.ts +198 -0
  49. package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
  50. package/src/admin/documentation-templates/index.ts +1 -1
  51. package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
  52. package/src/admin/free-consultation/index.ts +1 -1
  53. package/src/admin/index.ts +83 -83
  54. package/src/admin/logger/index.ts +78 -78
  55. package/src/admin/mailing/README.md +139 -139
  56. package/src/admin/mailing/appointment/appointment.mailing.service.ts +1253 -1253
  57. package/src/admin/mailing/appointment/index.ts +1 -1
  58. package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
  59. package/src/admin/mailing/base.mailing.service.ts +208 -208
  60. package/src/admin/mailing/clinicWelcome/clinicWelcome.mailing.ts +292 -292
  61. package/src/admin/mailing/clinicWelcome/index.ts +1 -1
  62. package/src/admin/mailing/clinicWelcome/templates/welcome.template.ts +225 -225
  63. package/src/admin/mailing/index.ts +5 -5
  64. package/src/admin/mailing/patientInvite/index.ts +2 -2
  65. package/src/admin/mailing/patientInvite/patientInvite.mailing.ts +415 -415
  66. package/src/admin/mailing/patientInvite/templates/invitation.template.ts +105 -105
  67. package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
  68. package/src/admin/mailing/practitionerInvite/index.ts +2 -2
  69. package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
  70. package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
  71. package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
  72. package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
  73. package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
  74. package/src/admin/notifications/index.ts +1 -1
  75. package/src/admin/notifications/notifications.admin.ts +818 -818
  76. package/src/admin/requirements/README.md +128 -128
  77. package/src/admin/requirements/index.ts +1 -1
  78. package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
  79. package/src/admin/users/index.ts +1 -1
  80. package/src/admin/users/user-profile.admin.ts +405 -405
  81. package/src/backoffice/constants/certification.constants.ts +13 -13
  82. package/src/backoffice/constants/index.ts +1 -1
  83. package/src/backoffice/errors/backoffice.errors.ts +181 -181
  84. package/src/backoffice/errors/index.ts +1 -1
  85. package/src/backoffice/expo-safe/README.md +26 -26
  86. package/src/backoffice/expo-safe/index.ts +41 -41
  87. package/src/backoffice/index.ts +5 -5
  88. package/src/backoffice/services/FIXES_README.md +102 -102
  89. package/src/backoffice/services/README.md +57 -57
  90. package/src/backoffice/services/analytics.service.proposal.md +863 -863
  91. package/src/backoffice/services/analytics.service.summary.md +143 -143
  92. package/src/backoffice/services/brand.service.ts +260 -260
  93. package/src/backoffice/services/category.service.ts +384 -384
  94. package/src/backoffice/services/constants.service.ts +385 -385
  95. package/src/backoffice/services/documentation-template.service.ts +202 -202
  96. package/src/backoffice/services/index.ts +10 -10
  97. package/src/backoffice/services/migrate-products.ts +116 -116
  98. package/src/backoffice/services/product.service.ts +557 -557
  99. package/src/backoffice/services/requirement.service.ts +235 -235
  100. package/src/backoffice/services/subcategory.service.ts +461 -461
  101. package/src/backoffice/services/technology.service.ts +1153 -1153
  102. package/src/backoffice/types/README.md +12 -12
  103. package/src/backoffice/types/admin-constants.types.ts +69 -69
  104. package/src/backoffice/types/brand.types.ts +29 -29
  105. package/src/backoffice/types/category.types.ts +67 -67
  106. package/src/backoffice/types/documentation-templates.types.ts +28 -28
  107. package/src/backoffice/types/index.ts +10 -10
  108. package/src/backoffice/types/procedure-product.types.ts +38 -38
  109. package/src/backoffice/types/product.types.ts +239 -239
  110. package/src/backoffice/types/requirement.types.ts +63 -63
  111. package/src/backoffice/types/static/README.md +18 -18
  112. package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
  113. package/src/backoffice/types/static/certification.types.ts +37 -37
  114. package/src/backoffice/types/static/contraindication.types.ts +19 -19
  115. package/src/backoffice/types/static/index.ts +6 -6
  116. package/src/backoffice/types/static/pricing.types.ts +16 -16
  117. package/src/backoffice/types/static/procedure-family.types.ts +14 -14
  118. package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
  119. package/src/backoffice/types/subcategory.types.ts +34 -34
  120. package/src/backoffice/types/technology.types.ts +168 -168
  121. package/src/backoffice/validations/index.ts +1 -1
  122. package/src/backoffice/validations/schemas.ts +164 -164
  123. package/src/config/__mocks__/firebase.ts +99 -99
  124. package/src/config/firebase.ts +78 -78
  125. package/src/config/index.ts +17 -17
  126. package/src/config/tiers.config.ts +255 -229
  127. package/src/errors/auth.error.ts +6 -6
  128. package/src/errors/auth.errors.ts +211 -211
  129. package/src/errors/clinic.errors.ts +32 -32
  130. package/src/errors/firebase.errors.ts +47 -47
  131. package/src/errors/user.errors.ts +99 -99
  132. package/src/index.backup.ts +407 -407
  133. package/src/index.ts +6 -6
  134. package/src/locales/en.ts +31 -31
  135. package/src/recommender/admin/index.ts +1 -1
  136. package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
  137. package/src/recommender/front/index.ts +1 -1
  138. package/src/recommender/front/services/onboarding.service.ts +5 -5
  139. package/src/recommender/front/services/recommender.service.ts +3 -3
  140. package/src/recommender/index.ts +1 -1
  141. package/src/services/PATIENTAUTH.MD +197 -197
  142. package/src/services/README.md +106 -106
  143. package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
  144. package/src/services/__tests__/auth/auth.setup.ts +298 -298
  145. package/src/services/__tests__/auth.service.test.ts +310 -310
  146. package/src/services/__tests__/base.service.test.ts +36 -36
  147. package/src/services/__tests__/user.service.test.ts +530 -530
  148. package/src/services/analytics/ARCHITECTURE.md +199 -199
  149. package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -225
  150. package/src/services/analytics/GROUPED_ANALYTICS.md +501 -501
  151. package/src/services/analytics/QUICK_START.md +393 -393
  152. package/src/services/analytics/README.md +304 -304
  153. package/src/services/analytics/SUMMARY.md +141 -141
  154. package/src/services/analytics/TRENDS.md +380 -380
  155. package/src/services/analytics/USAGE_GUIDE.md +518 -518
  156. package/src/services/analytics/analytics-cloud.service.ts +222 -222
  157. package/src/services/analytics/analytics.service.ts +2148 -2148
  158. package/src/services/analytics/index.ts +4 -4
  159. package/src/services/analytics/review-analytics.service.ts +941 -941
  160. package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -138
  161. package/src/services/analytics/utils/cost-calculation.utils.ts +182 -182
  162. package/src/services/analytics/utils/grouping.utils.ts +434 -434
  163. package/src/services/analytics/utils/stored-analytics.utils.ts +347 -347
  164. package/src/services/analytics/utils/time-calculation.utils.ts +186 -186
  165. package/src/services/analytics/utils/trend-calculation.utils.ts +200 -200
  166. package/src/services/appointment/README.md +17 -17
  167. package/src/services/appointment/appointment.service.ts +2943 -2941
  168. package/src/services/appointment/index.ts +1 -1
  169. package/src/services/appointment/utils/appointment.utils.ts +620 -620
  170. package/src/services/appointment/utils/extended-procedure.utils.ts +354 -354
  171. package/src/services/appointment/utils/form-initialization.utils.ts +516 -516
  172. package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
  173. package/src/services/appointment/utils/zone-management.utils.ts +468 -468
  174. package/src/services/appointment/utils/zone-photo.utils.ts +302 -302
  175. package/src/services/auth/auth.service.ts +1435 -1435
  176. package/src/services/auth/auth.v2.service.ts +961 -961
  177. package/src/services/auth/index.ts +7 -7
  178. package/src/services/auth/utils/error.utils.ts +90 -90
  179. package/src/services/auth/utils/firebase.utils.ts +49 -49
  180. package/src/services/auth/utils/index.ts +21 -21
  181. package/src/services/auth/utils/practitioner.utils.ts +125 -125
  182. package/src/services/base.service.ts +41 -41
  183. package/src/services/calendar/calendar.service.ts +1077 -1077
  184. package/src/services/calendar/calendar.v2.service.ts +1693 -1693
  185. package/src/services/calendar/calendar.v3.service.ts +313 -313
  186. package/src/services/calendar/externalCalendar.service.ts +178 -178
  187. package/src/services/calendar/index.ts +5 -5
  188. package/src/services/calendar/synced-calendars.service.ts +743 -743
  189. package/src/services/calendar/utils/appointment.utils.ts +265 -265
  190. package/src/services/calendar/utils/calendar-event.utils.ts +676 -676
  191. package/src/services/calendar/utils/clinic.utils.ts +237 -237
  192. package/src/services/calendar/utils/docs.utils.ts +157 -157
  193. package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
  194. package/src/services/calendar/utils/index.ts +8 -8
  195. package/src/services/calendar/utils/patient.utils.ts +198 -198
  196. package/src/services/calendar/utils/practitioner.utils.ts +221 -221
  197. package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
  198. package/src/services/clinic/README.md +204 -204
  199. package/src/services/clinic/__tests__/clinic-admin.service.test.ts +265 -265
  200. package/src/services/clinic/__tests__/clinic-group.service.test.ts +222 -222
  201. package/src/services/clinic/__tests__/clinic.service.test.ts +302 -302
  202. package/src/services/clinic/billing-transactions.service.ts +217 -217
  203. package/src/services/clinic/clinic-admin.service.ts +202 -202
  204. package/src/services/clinic/clinic-group.service.ts +310 -310
  205. package/src/services/clinic/clinic.service.ts +720 -720
  206. package/src/services/clinic/index.ts +5 -5
  207. package/src/services/clinic/practitioner-invite.service.ts +519 -519
  208. package/src/services/clinic/utils/admin.utils.ts +551 -551
  209. package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
  210. package/src/services/clinic/utils/clinic.utils.ts +1023 -1023
  211. package/src/services/clinic/utils/filter.utils.d.ts +23 -23
  212. package/src/services/clinic/utils/filter.utils.ts +462 -462
  213. package/src/services/clinic/utils/index.ts +10 -10
  214. package/src/services/clinic/utils/photos.utils.ts +188 -188
  215. package/src/services/clinic/utils/search.utils.ts +83 -83
  216. package/src/services/clinic/utils/tag.utils.ts +124 -124
  217. package/src/services/documentation-templates/documentation-template.service.ts +537 -537
  218. package/src/services/documentation-templates/filled-document.service.ts +597 -597
  219. package/src/services/documentation-templates/index.ts +2 -2
  220. package/src/services/index.ts +16 -15
  221. package/src/services/media/index.ts +1 -1
  222. package/src/services/media/media.service.ts +418 -418
  223. package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
  224. package/src/services/notifications/index.ts +1 -1
  225. package/src/services/notifications/notification.service.ts +215 -215
  226. package/src/services/patient/README.md +48 -48
  227. package/src/services/patient/To-Do.md +43 -43
  228. package/src/services/patient/__tests__/patient.service.test.ts +286 -286
  229. package/src/services/patient/index.ts +2 -2
  230. package/src/services/patient/patient.service.ts +1021 -1021
  231. package/src/services/patient/patientRequirements.service.ts +309 -309
  232. package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
  233. package/src/services/patient/utils/body-assessment.utils.ts +159 -159
  234. package/src/services/patient/utils/clinic.utils.ts +159 -159
  235. package/src/services/patient/utils/docs.utils.ts +142 -142
  236. package/src/services/patient/utils/hair-scalp-assessment.utils.ts +158 -158
  237. package/src/services/patient/utils/index.ts +9 -9
  238. package/src/services/patient/utils/location.utils.ts +126 -126
  239. package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
  240. package/src/services/patient/utils/medical.utils.ts +458 -458
  241. package/src/services/patient/utils/practitioner.utils.ts +260 -260
  242. package/src/services/patient/utils/pre-surgical-assessment.utils.ts +161 -161
  243. package/src/services/patient/utils/profile.utils.ts +510 -510
  244. package/src/services/patient/utils/sensitive.utils.ts +260 -260
  245. package/src/services/patient/utils/skin-quality-assessment.utils.ts +160 -160
  246. package/src/services/patient/utils/token.utils.ts +211 -211
  247. package/src/services/practitioner/README.md +145 -145
  248. package/src/services/practitioner/index.ts +1 -1
  249. package/src/services/practitioner/practitioner.service.ts +2355 -2354
  250. package/src/services/procedure/README.md +163 -163
  251. package/src/services/procedure/index.ts +1 -1
  252. package/src/services/procedure/procedure.service.ts +2521 -2521
  253. package/src/services/resource/README.md +119 -0
  254. package/src/services/resource/index.ts +1 -0
  255. package/src/services/resource/resource.service.ts +555 -0
  256. package/src/services/reviews/index.ts +1 -1
  257. package/src/services/reviews/reviews.service.ts +745 -745
  258. package/src/services/tier-enforcement.ts +240 -240
  259. package/src/services/user/index.ts +1 -1
  260. package/src/services/user/user.service.ts +533 -533
  261. package/src/services/user/user.v2.service.ts +467 -467
  262. package/src/types/analytics/analytics.types.ts +597 -597
  263. package/src/types/analytics/grouped-analytics.types.ts +173 -173
  264. package/src/types/analytics/index.ts +4 -4
  265. package/src/types/analytics/stored-analytics.types.ts +137 -137
  266. package/src/types/appointment/index.ts +524 -517
  267. package/src/types/calendar/index.ts +261 -260
  268. package/src/types/calendar/synced-calendar.types.ts +66 -66
  269. package/src/types/clinic/index.ts +530 -529
  270. package/src/types/clinic/practitioner-invite.types.ts +91 -91
  271. package/src/types/clinic/preferences.types.ts +159 -159
  272. package/src/types/clinic/rbac.types.ts +64 -63
  273. package/src/types/clinic/to-do +3 -3
  274. package/src/types/documentation-templates/index.ts +308 -308
  275. package/src/types/index.ts +50 -47
  276. package/src/types/notifications/README.md +77 -77
  277. package/src/types/notifications/index.ts +300 -300
  278. package/src/types/patient/aesthetic-analysis.types.ts +66 -66
  279. package/src/types/patient/allergies.ts +58 -58
  280. package/src/types/patient/body-assessment.types.ts +93 -93
  281. package/src/types/patient/hair-scalp-assessment.types.ts +98 -98
  282. package/src/types/patient/index.ts +279 -279
  283. package/src/types/patient/medical-info.types.ts +152 -152
  284. package/src/types/patient/patient-requirements.ts +92 -92
  285. package/src/types/patient/pre-surgical-assessment.types.ts +95 -95
  286. package/src/types/patient/skin-quality-assessment.types.ts +105 -105
  287. package/src/types/patient/token.types.ts +61 -61
  288. package/src/types/practitioner/index.ts +208 -208
  289. package/src/types/procedure/index.ts +189 -183
  290. package/src/types/profile/index.ts +39 -39
  291. package/src/types/resource/README.md +153 -0
  292. package/src/types/resource/index.ts +199 -0
  293. package/src/types/reviews/index.ts +132 -132
  294. package/src/types/tz-lookup.d.ts +4 -4
  295. package/src/types/user/index.ts +60 -60
  296. package/src/utils/TIMESTAMPS.md +176 -176
  297. package/src/utils/TimestampUtils.ts +241 -241
  298. package/src/utils/index.ts +1 -1
  299. package/src/validations/README.md +94 -0
  300. package/src/validations/appointment.schema.ts +589 -589
  301. package/src/validations/calendar.schema.ts +225 -225
  302. package/src/validations/clinic.schema.ts +494 -494
  303. package/src/validations/common.schema.ts +25 -25
  304. package/src/validations/documentation-templates/index.ts +1 -1
  305. package/src/validations/documentation-templates/template.schema.ts +220 -220
  306. package/src/validations/documentation-templates.schema.ts +10 -10
  307. package/src/validations/index.ts +21 -20
  308. package/src/validations/media.schema.ts +10 -10
  309. package/src/validations/notification.schema.ts +90 -90
  310. package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
  311. package/src/validations/patient/body-assessment.schema.ts +82 -82
  312. package/src/validations/patient/hair-scalp-assessment.schema.ts +70 -70
  313. package/src/validations/patient/medical-info.schema.ts +177 -177
  314. package/src/validations/patient/patient-requirements.schema.ts +84 -84
  315. package/src/validations/patient/pre-surgical-assessment.schema.ts +78 -78
  316. package/src/validations/patient/skin-quality-assessment.schema.ts +70 -70
  317. package/src/validations/patient/token.schema.ts +29 -29
  318. package/src/validations/patient.schema.ts +217 -217
  319. package/src/validations/practitioner.schema.ts +224 -224
  320. package/src/validations/procedure-product.schema.ts +41 -41
  321. package/src/validations/procedure.schema.ts +136 -124
  322. package/src/validations/profile-info.schema.ts +41 -41
  323. package/src/validations/resource.schema.ts +57 -0
  324. package/src/validations/reviews.schema.ts +195 -195
  325. package/src/validations/schemas.ts +109 -109
  326. package/src/validations/shared.schema.ts +78 -78
@@ -1,1021 +1,1021 @@
1
- import {
2
- doc,
3
- getDoc,
4
- writeBatch,
5
- updateDoc,
6
- arrayUnion,
7
- serverTimestamp,
8
- } from 'firebase/firestore';
9
- import { BaseService } from '../base.service';
10
- import { MediaService, MediaAccessLevel, MediaResource } from '../media/media.service';
11
- import {
12
- PatientProfile,
13
- PatientSensitiveInfo,
14
- PatientLocationInfo,
15
- CreatePatientProfileData,
16
- CreatePatientSensitiveInfoData,
17
- UpdatePatientSensitiveInfoData,
18
- CreatePatientLocationInfoData,
19
- UpdatePatientLocationInfoData,
20
- PatientMedicalInfo,
21
- CreatePatientMedicalInfoData,
22
- UpdateVitalStatsData,
23
- AddAllergyData,
24
- UpdateAllergyData,
25
- AddBlockingConditionData,
26
- UpdateBlockingConditionData,
27
- AddContraindicationData,
28
- UpdateContraindicationData,
29
- AddMedicationData,
30
- UpdateMedicationData,
31
- PatientDoctor,
32
- PatientClinic,
33
- SearchPatientsParams,
34
- RequesterInfo,
35
- PatientProfileForDoctor,
36
- CreateManualPatientData,
37
- } from '../../types/patient';
38
- import { Auth } from 'firebase/auth';
39
- import { Firestore } from 'firebase/firestore';
40
- import { FirebaseApp } from 'firebase/app';
41
- import { Timestamp } from 'firebase/firestore';
42
-
43
- // Importujemo utility funkcije
44
- import {
45
- createPatientProfileUtil,
46
- getPatientProfileUtil,
47
- getPatientProfileByUserRefUtil,
48
- addExpoTokenUtil,
49
- removeExpoTokenUtil,
50
- addPointsUtil,
51
- updatePatientProfileUtil,
52
- updatePatientProfileByUserRefUtil,
53
- searchPatientsUtil,
54
- getAllPatientsUtil,
55
- updatePatientLocationUtil,
56
- createLocationInfoUtil,
57
- getLocationInfoUtil,
58
- updateLocationInfoUtil,
59
- createSensitiveInfoUtil,
60
- getSensitiveInfoUtil,
61
- updateSensitiveInfoUtil,
62
- claimPatientSensitiveInfoUtil,
63
- createMedicalInfoUtil,
64
- getMedicalInfoUtil,
65
- updateVitalStatsUtil,
66
- addAllergyUtil,
67
- updateAllergyUtil,
68
- removeAllergyUtil,
69
- addBlockingConditionUtil,
70
- updateBlockingConditionUtil,
71
- removeBlockingConditionUtil,
72
- addContraindicationUtil,
73
- updateContraindicationUtil,
74
- removeContraindicationUtil,
75
- addMedicationUtil,
76
- updateMedicationUtil,
77
- removeMedicationUtil,
78
- getPatientDocRef,
79
- getSensitiveInfoDocRef,
80
- getLocationInfoDocRef,
81
- getMedicalInfoDocRef,
82
- addDoctorUtil,
83
- removeDoctorUtil,
84
- addClinicUtil,
85
- removeClinicUtil,
86
- getPatientsByPractitionerUtil,
87
- getPatientsByPractitionerWithDetailsUtil,
88
- getPatientsByClinicUtil,
89
- getPatientsByClinicWithDetailsUtil,
90
- createPatientTokenUtil,
91
- validatePatientTokenUtil,
92
- markPatientTokenAsUsedUtil,
93
- getActiveInviteTokensByClinicUtil,
94
- getActiveInviteTokensByPatientUtil,
95
- } from './utils';
96
-
97
- import {
98
- getAestheticAnalysisUtil,
99
- createOrUpdateAestheticAnalysisUtil,
100
- } from './utils/aesthetic-analysis.utils';
101
-
102
- import {
103
- AestheticAnalysis,
104
- CreateAestheticAnalysisData,
105
- UpdateAestheticAnalysisData,
106
- SkinQualityAssessment,
107
- CreateSkinQualityAssessmentData,
108
- UpdateSkinQualityAssessmentData,
109
- BodyAssessment,
110
- CreateBodyAssessmentData,
111
- UpdateBodyAssessmentData,
112
- HairScalpAssessment,
113
- CreateHairScalpAssessmentData,
114
- UpdateHairScalpAssessmentData,
115
- PreSurgicalAssessment,
116
- CreatePreSurgicalAssessmentData,
117
- UpdatePreSurgicalAssessmentData,
118
- } from '../../types/patient';
119
-
120
- import {
121
- getSkinQualityAssessmentUtil,
122
- createOrUpdateSkinQualityAssessmentUtil,
123
- } from './utils/skin-quality-assessment.utils';
124
-
125
- import {
126
- getBodyAssessmentUtil,
127
- createOrUpdateBodyAssessmentUtil,
128
- } from './utils/body-assessment.utils';
129
-
130
- import {
131
- getHairScalpAssessmentUtil,
132
- createOrUpdateHairScalpAssessmentUtil,
133
- } from './utils/hair-scalp-assessment.utils';
134
-
135
- import {
136
- getPreSurgicalAssessmentUtil,
137
- createOrUpdatePreSurgicalAssessmentUtil,
138
- } from './utils/pre-surgical-assessment.utils';
139
-
140
- import { CreatePatientTokenData, PatientToken } from '../../types/patient/token.types';
141
-
142
- export class PatientService extends BaseService {
143
- private mediaService: MediaService;
144
-
145
- constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
146
- super(db, auth, app);
147
- this.mediaService = new MediaService(db, auth, app);
148
- }
149
-
150
- // Metode za rad sa profilom pacijenta
151
- async createPatientProfile(data: CreatePatientProfileData): Promise<PatientProfile> {
152
- return createPatientProfileUtil(this.db, data, () => this.generateId());
153
- }
154
-
155
- async getPatientProfile(patientId: string): Promise<PatientProfile | null> {
156
- return getPatientProfileUtil(this.db, patientId);
157
- }
158
-
159
- /**
160
- * Manually creates a new patient profile, typically initiated by a clinic admin.
161
- * This creates a patient record that is not initially linked to an authenticated user.
162
- *
163
- * @param {CreateManualPatientData} data - The data for the new patient.
164
- * @param {RequesterInfo} requester - Information about the user creating the patient (must be a clinic admin).
165
- * @returns {Promise<PatientProfile>} The newly created patient profile.
166
- * @throws {Error} If the requester is not a valid clinic admin.
167
- */
168
- async createManualPatient(
169
- data: CreateManualPatientData,
170
- requester: RequesterInfo,
171
- ): Promise<PatientProfile> {
172
- console.log(
173
- `[PatientService.createManualPatient] Attempting to create manual patient by requester:`,
174
- requester,
175
- );
176
-
177
- // Security Check: Ensure the requester is a clinic admin
178
- if (
179
- requester.role !== 'clinic_admin' ||
180
- !requester.associatedClinicId ||
181
- requester.associatedClinicId !== data.clinicId
182
- ) {
183
- throw new Error(
184
- 'Unauthorized: Requester must be a clinic admin and can only add patients to their own clinic.',
185
- );
186
- }
187
-
188
- const patientId = this.generateId();
189
- const batch = writeBatch(this.db);
190
- const now = Timestamp.now();
191
-
192
- // 1. Create Patient Profile
193
- const patientProfileRef = getPatientDocRef(this.db, patientId);
194
- const newProfile: PatientProfile = {
195
- id: patientId,
196
- displayName: `${data.firstName} ${data.lastName.charAt(0)}.`,
197
- expoTokens: [],
198
- gamification: { level: 1, points: 0 },
199
- isActive: true,
200
- isVerified: false, // Manual profiles are not verified by default
201
- isManual: true,
202
- doctors: [],
203
- clinics: [
204
- {
205
- clinicId: data.clinicId,
206
- assignedAt: now,
207
- assignedBy: requester.id,
208
- isActive: true,
209
- notes: data.notes,
210
- },
211
- ],
212
- doctorIds: [],
213
- clinicIds: [data.clinicId],
214
- createdAt: now,
215
- updatedAt: now,
216
- phoneNumber: data.phoneNumber,
217
- dateOfBirth: data.dateOfBirth,
218
- };
219
- batch.set(patientProfileRef, newProfile);
220
-
221
- // 2. Create Patient Sensitive Info
222
- const sensitiveInfoRef = getSensitiveInfoDocRef(this.db, patientId);
223
- const newSensitiveInfo: Omit<PatientSensitiveInfo, 'photoUrl'> = {
224
- patientId,
225
- firstName: data.firstName,
226
- lastName: data.lastName,
227
- dateOfBirth: data.dateOfBirth,
228
- gender: data.gender,
229
- email: data.email,
230
- phoneNumber: data.phoneNumber,
231
- addressData: data.addressData,
232
- emergencyContacts: [],
233
- createdAt: now,
234
- updatedAt: now,
235
- };
236
- batch.set(sensitiveInfoRef, newSensitiveInfo);
237
-
238
- // 3. Create Patient Medical Info
239
- const medicalInfoRef = getMedicalInfoDocRef(this.db, patientId);
240
- const newMedicalInfo: PatientMedicalInfo = {
241
- patientId,
242
- vitalStats: {},
243
- blockingConditions: [],
244
- contraindications: [],
245
- allergies: [],
246
- currentMedications: [],
247
- emergencyNotes: '',
248
- lastUpdated: now,
249
- updatedBy: requester.id, // The admin who created the record
250
- };
251
- batch.set(medicalInfoRef, newMedicalInfo);
252
-
253
- await batch.commit();
254
-
255
- console.log(
256
- `[PatientService.createManualPatient] Successfully created manual patient with ID: ${patientId}`,
257
- );
258
-
259
- return newProfile;
260
- }
261
-
262
- async getPatientProfileByUserRef(userRef: string): Promise<PatientProfile | null> {
263
- return getPatientProfileByUserRefUtil(this.db, userRef);
264
- }
265
-
266
- // Metode za rad sa lokacijskim podacima
267
- async updatePatientLocation(
268
- patientId: string,
269
- latitude: number,
270
- longitude: number,
271
- ): Promise<void> {
272
- await updatePatientLocationUtil(this.db, patientId, latitude, longitude);
273
- }
274
-
275
- async updatePatientLocationByUserRef(
276
- userRef: string,
277
- latitude: number,
278
- longitude: number,
279
- ): Promise<void> {
280
- const profile = await this.getPatientProfileByUserRef(userRef);
281
- if (!profile) throw new Error('Patient profile not found');
282
- await this.updatePatientLocation(profile.id, latitude, longitude);
283
- }
284
-
285
- async createLocationInfo(
286
- data: CreatePatientLocationInfoData,
287
- requesterId: string,
288
- ): Promise<PatientLocationInfo> {
289
- return createLocationInfoUtil(this.db, data, requesterId);
290
- }
291
-
292
- async getLocationInfo(
293
- patientId: string,
294
- requesterId: string,
295
- ): Promise<PatientLocationInfo | null> {
296
- return getLocationInfoUtil(this.db, patientId, requesterId);
297
- }
298
-
299
- async getLocationInfoByUserRef(
300
- userRef: string,
301
- requesterId: string,
302
- ): Promise<PatientLocationInfo | null> {
303
- const profile = await this.getPatientProfileByUserRef(userRef);
304
- if (!profile) return null;
305
- return this.getLocationInfo(profile.id, requesterId);
306
- }
307
-
308
- async updateLocationInfo(
309
- patientId: string,
310
- data: UpdatePatientLocationInfoData,
311
- requesterId: string,
312
- ): Promise<PatientLocationInfo> {
313
- return updateLocationInfoUtil(this.db, patientId, data, requesterId);
314
- }
315
-
316
- // Metode za rad sa osetljivim informacijama
317
- async createSensitiveInfo(
318
- data: CreatePatientSensitiveInfoData,
319
- requesterUserId: string,
320
- ): Promise<PatientSensitiveInfo> {
321
- const currentUser = await this.getCurrentUser();
322
- if (currentUser.uid !== requesterUserId) {
323
- throw new Error('Requester does not match authenticated user.');
324
- }
325
-
326
- return createSensitiveInfoUtil(
327
- this.db,
328
- data,
329
- requesterUserId,
330
- currentUser.roles,
331
- this.mediaService,
332
- );
333
- }
334
-
335
- async getSensitiveInfo(
336
- patientId: string,
337
- requesterUserId: string,
338
- ): Promise<PatientSensitiveInfo | null> {
339
- const currentUser = await this.getCurrentUser();
340
- if (currentUser.uid !== requesterUserId) {
341
- // Allow for read-only access if authorized, but for now we check identity
342
- // This could be expanded later based on practitioner/admin roles
343
- }
344
- return getSensitiveInfoUtil(this.db, patientId, requesterUserId, currentUser.roles);
345
- }
346
-
347
- async getSensitiveInfoByUserRef(
348
- userRef: string,
349
- requesterUserId: string,
350
- ): Promise<PatientSensitiveInfo | null> {
351
- const profile = await this.getPatientProfileByUserRef(userRef);
352
- if (!profile) return null;
353
- // We pass requesterUserId which is the UID of the one asking.
354
- return this.getSensitiveInfo(profile.id, requesterUserId);
355
- }
356
-
357
- async updateSensitiveInfo(
358
- patientId: string,
359
- data: UpdatePatientSensitiveInfoData,
360
- requesterUserId: string,
361
- ): Promise<PatientSensitiveInfo> {
362
- const currentUser = await this.getCurrentUser();
363
- if (currentUser.uid !== requesterUserId) {
364
- throw new Error('Requester does not match authenticated user.');
365
- }
366
- return updateSensitiveInfoUtil(
367
- this.db,
368
- patientId,
369
- data,
370
- requesterUserId,
371
- currentUser.roles,
372
- this.mediaService,
373
- );
374
- }
375
-
376
- async claimPatientSensitiveInfo(
377
- patientId: string,
378
- userId: string,
379
- ): Promise<PatientSensitiveInfo> {
380
- return claimPatientSensitiveInfoUtil(this.db, patientId, userId);
381
- }
382
-
383
- // Metode za rad sa medicinskim informacijama
384
- async createMedicalInfo(patientId: string, data: CreatePatientMedicalInfoData): Promise<void> {
385
- const currentUser = await this.getCurrentUser();
386
- await createMedicalInfoUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
387
- }
388
-
389
- async getMedicalInfo(patientId: string): Promise<PatientMedicalInfo> {
390
- const currentUser = await this.getCurrentUser();
391
- return getMedicalInfoUtil(this.db, patientId, currentUser.uid, currentUser.roles);
392
- }
393
-
394
- async getMedicalInfoByUserRef(userRef: string): Promise<PatientMedicalInfo> {
395
- const profile = await this.getPatientProfileByUserRef(userRef);
396
- if (!profile) throw new Error('Patient profile not found');
397
- return this.getMedicalInfo(profile.id);
398
- }
399
-
400
- // Metode za rad sa vitalnim statistikama
401
- async updateVitalStats(patientId: string, data: UpdateVitalStatsData): Promise<void> {
402
- const currentUser = await this.getCurrentUser();
403
- await updateVitalStatsUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
404
- }
405
-
406
- // Metode za rad sa alergijama
407
- async addAllergy(patientId: string, data: AddAllergyData): Promise<void> {
408
- const currentUser = await this.getCurrentUser();
409
- await addAllergyUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
410
- }
411
-
412
- async updateAllergy(patientId: string, data: UpdateAllergyData): Promise<void> {
413
- const currentUser = await this.getCurrentUser();
414
- await updateAllergyUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
415
- }
416
-
417
- async removeAllergy(patientId: string, allergyIndex: number): Promise<void> {
418
- const currentUser = await this.getCurrentUser();
419
- await removeAllergyUtil(this.db, patientId, allergyIndex, currentUser.uid, currentUser.roles);
420
- }
421
-
422
- // Metode za rad sa blocking conditions
423
- async addBlockingCondition(patientId: string, data: AddBlockingConditionData): Promise<void> {
424
- const currentUser = await this.getCurrentUser();
425
- await addBlockingConditionUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
426
- }
427
-
428
- async updateBlockingCondition(
429
- patientId: string,
430
- data: UpdateBlockingConditionData,
431
- ): Promise<void> {
432
- const currentUser = await this.getCurrentUser();
433
- await updateBlockingConditionUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
434
- }
435
-
436
- async removeBlockingCondition(patientId: string, conditionIndex: number): Promise<void> {
437
- const currentUser = await this.getCurrentUser();
438
- await removeBlockingConditionUtil(
439
- this.db,
440
- patientId,
441
- conditionIndex,
442
- currentUser.uid,
443
- currentUser.roles,
444
- );
445
- }
446
-
447
- // Metode za rad sa kontraindikacijama
448
- async addContraindication(patientId: string, data: AddContraindicationData): Promise<void> {
449
- const currentUser = await this.getCurrentUser();
450
- await addContraindicationUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
451
- }
452
-
453
- async updateContraindication(patientId: string, data: UpdateContraindicationData): Promise<void> {
454
- const currentUser = await this.getCurrentUser();
455
- await updateContraindicationUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
456
- }
457
-
458
- async removeContraindication(patientId: string, contraindicationIndex: number): Promise<void> {
459
- const currentUser = await this.getCurrentUser();
460
- await removeContraindicationUtil(
461
- this.db,
462
- patientId,
463
- contraindicationIndex,
464
- currentUser.uid,
465
- currentUser.roles,
466
- );
467
- }
468
-
469
- // Metode za rad sa medikacijama
470
- async addMedication(patientId: string, data: AddMedicationData): Promise<void> {
471
- const currentUser = await this.getCurrentUser();
472
- await addMedicationUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
473
- }
474
-
475
- async updateMedication(patientId: string, data: UpdateMedicationData): Promise<void> {
476
- const currentUser = await this.getCurrentUser();
477
- await updateMedicationUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
478
- }
479
-
480
- async removeMedication(patientId: string, medicationIndex: number): Promise<void> {
481
- const currentUser = await this.getCurrentUser();
482
- await removeMedicationUtil(
483
- this.db,
484
- patientId,
485
- medicationIndex,
486
- currentUser.uid,
487
- currentUser.roles,
488
- );
489
- }
490
-
491
- // Pomoćne metode
492
- async addExpoToken(patientId: string, token: string): Promise<void> {
493
- await addExpoTokenUtil(this.db, patientId, token);
494
- }
495
-
496
- async addExpoTokenByUserRef(userRef: string, token: string): Promise<void> {
497
- const profile = await this.getPatientProfileByUserRef(userRef);
498
- if (!profile) throw new Error('Patient profile not found');
499
- await this.addExpoToken(profile.id, token);
500
- }
501
-
502
- async removeExpoToken(patientId: string, token: string): Promise<void> {
503
- await removeExpoTokenUtil(this.db, patientId, token);
504
- }
505
-
506
- async removeExpoTokenByUserRef(userRef: string, token: string): Promise<void> {
507
- const profile = await this.getPatientProfileByUserRef(userRef);
508
- if (!profile) throw new Error('Patient profile not found');
509
- await this.removeExpoToken(profile.id, token);
510
- }
511
-
512
- async addPoints(patientId: string, points: number): Promise<void> {
513
- await addPointsUtil(this.db, patientId, points);
514
- }
515
-
516
- async addPointsByUserRef(userRef: string, points: number): Promise<void> {
517
- const profile = await this.getPatientProfileByUserRef(userRef);
518
- if (!profile) throw new Error('Patient profile not found');
519
- await this.addPoints(profile.id, points);
520
- }
521
-
522
- private async getCurrentUser(): Promise<any> {
523
- if (!this.auth.currentUser) {
524
- throw new Error('No authenticated user');
525
- }
526
- const userDoc = await getDoc(doc(this.db, 'users', this.auth.currentUser.uid));
527
- if (!userDoc.exists()) {
528
- throw new Error('User not found');
529
- }
530
- return userDoc.data();
531
- }
532
-
533
- /**
534
- * Briše profil pacijenta i sve povezane subkolekcije
535
- */
536
- async deletePatientProfile(patientId: string): Promise<void> {
537
- const batch = writeBatch(this.db);
538
-
539
- // Brisanje sensitive info
540
- batch.delete(getSensitiveInfoDocRef(this.db, patientId));
541
-
542
- // Brisanje location info
543
- batch.delete(getLocationInfoDocRef(this.db, patientId));
544
-
545
- // Brisanje medical info
546
- batch.delete(getMedicalInfoDocRef(this.db, patientId));
547
-
548
- // Brisanje glavnog profila
549
- batch.delete(getPatientDocRef(this.db, patientId));
550
-
551
- await batch.commit();
552
- }
553
-
554
- // Metode za rad sa doktorima
555
- async addDoctor(patientId: string, doctorRef: string): Promise<void> {
556
- const currentUser = await this.getCurrentUser();
557
- await addDoctorUtil(this.db, patientId, doctorRef, currentUser.uid);
558
- }
559
-
560
- async removeDoctor(patientId: string, doctorRef: string): Promise<void> {
561
- await removeDoctorUtil(this.db, patientId, doctorRef);
562
- }
563
-
564
- // Metode za rad sa klinikama
565
- async addClinic(patientId: string, clinicId: string): Promise<void> {
566
- const currentUser = await this.getCurrentUser();
567
- await addClinicUtil(this.db, patientId, clinicId, currentUser.uid);
568
- }
569
-
570
- async removeClinic(patientId: string, clinicId: string): Promise<void> {
571
- await removeClinicUtil(this.db, patientId, clinicId);
572
- }
573
-
574
- // Metode za rad sa profilnom slikom
575
- /**
576
- * Uploads a profile photo for a patient
577
- * @param patientId - ID of the patient
578
- * @param file - File or Blob to upload
579
- * @returns URL of the uploaded photo
580
- */
581
- async uploadProfilePhoto(patientId: string, file: File | Blob): Promise<string> {
582
- console.log(`[PatientService] Uploading profile photo for patient ${patientId}`);
583
-
584
- const mediaMetadata = await this.mediaService.uploadMedia(
585
- file,
586
- patientId, // Using patientId as ownerId
587
- MediaAccessLevel.PRIVATE, // Profile photos should be private
588
- 'patient_profile_photos',
589
- file instanceof File ? file.name : `profile_photo_${patientId}`,
590
- );
591
-
592
- // Update the patient sensitive info with the new photo URL
593
- await updateDoc(getSensitiveInfoDocRef(this.db, patientId), {
594
- photoUrl: mediaMetadata.url,
595
- updatedAt: serverTimestamp(),
596
- });
597
-
598
- return mediaMetadata.url;
599
- }
600
-
601
- /**
602
- * Updates a patient's profile photo (replaces existing one)
603
- * @param patientId - ID of the patient
604
- * @param file - New file or Blob to upload
605
- * @returns URL of the new uploaded photo
606
- */
607
- async updateProfilePhoto(patientId: string, file: File | Blob): Promise<string> {
608
- console.log(`[PatientService] Updating profile photo for patient ${patientId}`);
609
-
610
- // Get current patient sensitive info to check for existing photo
611
- const currentUser = await this.getCurrentUser();
612
- const currentSensitiveInfo = await this.getSensitiveInfo(patientId, currentUser.uid);
613
-
614
- // Delete old photo if it exists and is managed by our MediaService
615
- if (currentSensitiveInfo?.photoUrl && typeof currentSensitiveInfo.photoUrl === 'string') {
616
- try {
617
- const existingMediaMetadata = await this.mediaService.getMediaMetadataByUrl(
618
- currentSensitiveInfo.photoUrl,
619
- );
620
- if (existingMediaMetadata) {
621
- await this.mediaService.deleteMedia(existingMediaMetadata.id);
622
- }
623
- } catch (error) {
624
- console.warn(
625
- `[PatientService] Could not delete old profile photo for patient ${patientId}:`,
626
- error,
627
- );
628
- // Continue with upload even if deletion fails
629
- }
630
- }
631
-
632
- // Upload new photo
633
- return this.uploadProfilePhoto(patientId, file);
634
- }
635
-
636
- /**
637
- * Deletes a patient's profile photo
638
- * @param patientId - ID of the patient
639
- */
640
- async deleteProfilePhoto(patientId: string): Promise<void> {
641
- console.log(`[PatientService] Deleting profile photo for patient ${patientId}`);
642
-
643
- // Get current patient sensitive info to find the photo URL
644
- const currentUser = await this.getCurrentUser();
645
- const currentSensitiveInfo = await this.getSensitiveInfo(patientId, currentUser.uid);
646
-
647
- if (currentSensitiveInfo?.photoUrl && typeof currentSensitiveInfo.photoUrl === 'string') {
648
- try {
649
- const existingMediaMetadata = await this.mediaService.getMediaMetadataByUrl(
650
- currentSensitiveInfo.photoUrl,
651
- );
652
- if (existingMediaMetadata) {
653
- await this.mediaService.deleteMedia(existingMediaMetadata.id);
654
- }
655
- } catch (error) {
656
- console.warn(
657
- `[PatientService] Could not delete profile photo for patient ${patientId}:`,
658
- error,
659
- );
660
- }
661
-
662
- // Remove photo URL from patient sensitive info
663
- await updateDoc(getSensitiveInfoDocRef(this.db, patientId), {
664
- photoUrl: null,
665
- updatedAt: serverTimestamp(),
666
- });
667
- }
668
- }
669
-
670
- /**
671
- * Handles profile photo upload for patients (supports MediaResource)
672
- * @param photoUrl - MediaResource (File, Blob, or URL string) from CreatePatientSensitiveInfoData
673
- * @param patientId - ID of the patient
674
- * @returns URL string of the uploaded or existing photo
675
- */
676
- private async handleProfilePhotoUpload(
677
- photoUrl: MediaResource | undefined,
678
- patientId: string,
679
- ): Promise<string | undefined> {
680
- if (!photoUrl) {
681
- return undefined;
682
- }
683
-
684
- // If it's already a URL string, return it as is
685
- if (typeof photoUrl === 'string') {
686
- return photoUrl;
687
- }
688
-
689
- // If it's a File or Blob, upload it
690
- if (photoUrl instanceof File || photoUrl instanceof Blob) {
691
- return this.uploadProfilePhoto(patientId, photoUrl);
692
- }
693
-
694
- return undefined;
695
- }
696
-
697
- // Metode za ažuriranje profila
698
- async updatePatientProfile(
699
- patientId: string,
700
- data: Partial<Omit<PatientProfile, 'id' | 'createdAt' | 'updatedAt'>>,
701
- ): Promise<PatientProfile> {
702
- return updatePatientProfileUtil(this.db, patientId, data);
703
- }
704
-
705
- async updatePatientProfileByUserRef(
706
- userRef: string,
707
- data: Partial<Omit<PatientProfile, 'id' | 'createdAt' | 'updatedAt'>>,
708
- ): Promise<PatientProfile> {
709
- return updatePatientProfileByUserRefUtil(this.db, userRef, data);
710
- }
711
-
712
- /**
713
- * Searches for patient profiles based on clinic/practitioner association.
714
- * Requires information about the requester for security checks.
715
- *
716
- * @param {SearchPatientsParams} params - The search criteria (clinicId, practitionerId).
717
- * @param {RequesterInfo} requester - Information about the user performing the search (ID, role, associated IDs).
718
- * @returns {Promise<PatientProfile[]>} A promise resolving to an array of matching patient profiles.
719
- */
720
- async searchPatients(
721
- params: SearchPatientsParams,
722
- requester: RequesterInfo,
723
- ): Promise<PatientProfile[]> {
724
- // We can potentially add more service-level logic here in the future,
725
- // like fetching additional data or enriching the results.
726
- // For now, we delegate directly to the utility function.
727
- console.log(
728
- `[PatientService.searchPatients] Initiating search with params:`,
729
- params,
730
- `by requester:`,
731
- requester,
732
- );
733
-
734
- // The utility function already handles validation and security checks.
735
- return searchPatientsUtil(this.db, params, requester);
736
- }
737
-
738
- /**
739
- * Gets all patient profiles.
740
- *
741
- * @param {Object} options - Optional parameters for pagination
742
- * @param {number} options.limit - Maximum number of profiles to return
743
- * @param {string} options.startAfter - The ID of the document to start after (for pagination)
744
- * @returns {Promise<PatientProfile[]>} A promise resolving to an array of all patient profiles.
745
- */
746
- async getAllPatients(options?: {
747
- limit?: number;
748
- startAfter?: string;
749
- }): Promise<PatientProfile[]> {
750
- console.log(`[PatientService.getAllPatients] Fetching patients with options:`, options);
751
- return getAllPatientsUtil(this.db, options);
752
- }
753
-
754
- /**
755
- * Gets all patients associated with a specific practitioner.
756
- *
757
- * @param {string} practitionerId - ID of the practitioner whose patients to retrieve
758
- * @param {Object} options - Optional parameters for pagination
759
- * @param {number} options.limit - Maximum number of profiles to return
760
- * @param {string} options.startAfter - The ID of the document to start after (for pagination)
761
- * @returns {Promise<PatientProfile[]>} A promise resolving to an array of patient profiles
762
- */
763
- async getPatientsByPractitioner(
764
- practitionerId: string,
765
- options?: {
766
- limit?: number;
767
- startAfter?: string;
768
- },
769
- ): Promise<PatientProfile[]> {
770
- console.log(
771
- `[PatientService.getPatientsByPractitioner] Fetching patients for practitioner: ${practitionerId}`,
772
- );
773
- return getPatientsByPractitionerUtil(this.db, practitionerId, options);
774
- }
775
-
776
- /**
777
- * Gets all patients associated with a specific practitioner with their sensitive information.
778
- *
779
- * @param {string} practitionerId - ID of the practitioner whose patients to retrieve
780
- * @param {Object} options - Optional parameters for pagination
781
- * @param {number} options.limit - Maximum number of profiles to return
782
- * @param {string} options.startAfter - The ID of the document to start after (for pagination)
783
- * @returns {Promise<PatientProfileForDoctor[]>} A promise resolving to an array of patient profiles with sensitive info
784
- */
785
- async getPatientsByPractitionerWithDetails(
786
- practitionerId: string,
787
- options?: {
788
- limit?: number;
789
- startAfter?: string;
790
- },
791
- ): Promise<PatientProfileForDoctor[]> {
792
- console.log(
793
- `[PatientService.getPatientsByPractitionerWithDetails] Fetching detailed patient profiles for practitioner: ${practitionerId}`,
794
- );
795
- return getPatientsByPractitionerWithDetailsUtil(this.db, practitionerId, options);
796
- }
797
-
798
- /**
799
- * Gets all patients associated with a specific clinic, including sensitive info.
800
- * This merges data from PatientProfile and PatientSensitiveInfo subcollection.
801
- *
802
- * @param {string} clinicId - ID of the clinic whose patients to retrieve
803
- * @param {Object} options - Optional parameters for pagination
804
- * @param {number} options.limit - Maximum number of profiles to return
805
- * @param {string} options.startAfter - The ID of the document to start after (for pagination)
806
- * @returns {Promise<PatientProfile[]>} A promise resolving to an array of patient profiles with merged sensitive info
807
- */
808
- async getPatientsByClinic(
809
- clinicId: string,
810
- options?: {
811
- limit?: number;
812
- startAfter?: string;
813
- },
814
- ): Promise<PatientProfile[]> {
815
- console.log(`[PatientService.getPatientsByClinic] Fetching patients with details for clinic: ${clinicId}`);
816
- return getPatientsByClinicWithDetailsUtil(this.db, clinicId, options);
817
- }
818
-
819
- /**
820
- * Creates a token for inviting a patient to claim their profile.
821
- *
822
- * @param {CreatePatientTokenData} data - Data for creating the token.
823
- * @param {string} createdBy - ID of the admin user creating the token.
824
- * @returns {Promise<PatientToken>} The created token.
825
- */
826
- async createPatientToken(data: CreatePatientTokenData, createdBy: string): Promise<PatientToken> {
827
- // We assume the 'createdBy' user is validated to be a clinic admin
828
- // in the calling context (e.g., a cloud function or API endpoint).
829
- return createPatientTokenUtil(
830
- this.db,
831
- data,
832
- createdBy,
833
- () => this.generateId(), // Pass the ID generation function
834
- );
835
- }
836
-
837
- /**
838
- * Validates a patient invitation token.
839
- *
840
- * @param {string} tokenString - The token string to validate.
841
- * @returns {Promise<PatientToken | null>} The token if found and valid, otherwise null.
842
- */
843
- async validatePatientToken(tokenString: string): Promise<PatientToken | null> {
844
- return validatePatientTokenUtil(this.db, tokenString);
845
- }
846
-
847
- /**
848
- * Marks a patient invitation token as used.
849
- *
850
- * @param {string} tokenId - The ID of the token to mark as used.
851
- * @param {string} patientId - The ID of the patient associated with the token.
852
- * @param {string} userId - The ID of the user who is using the token.
853
- * @returns {Promise<void>}
854
- */
855
- async markPatientTokenAsUsed(tokenId: string, patientId: string, userId: string): Promise<void> {
856
- return markPatientTokenAsUsedUtil(this.db, tokenId, patientId, userId);
857
- }
858
-
859
- /**
860
- * Retrieves all active invitation tokens for a specific clinic.
861
- * NOTE: This should be protected and only exposed to authorized clinic admins.
862
- *
863
- * @param {string} clinicId - The ID of the clinic.
864
- * @returns {Promise<PatientToken[]>} An array of active tokens for the clinic.
865
- */
866
- async getActiveInviteTokensByClinic(clinicId: string): Promise<PatientToken[]> {
867
- return getActiveInviteTokensByClinicUtil(this.db, clinicId);
868
- }
869
-
870
- /**
871
- * Retrieves all active invitation tokens for a specific patient.
872
- * NOTE: This should be protected and only exposed to authorized clinic admins.
873
- *
874
- * @param {string} patientId - The ID of the patient.
875
- * @returns {Promise<PatientToken[]>} An array of active tokens for the patient.
876
- */
877
- async getActiveInviteTokensByPatient(patientId: string): Promise<PatientToken[]> {
878
- // Security check should be done in the calling context to ensure
879
- // the admin has permission to view this patient's tokens.
880
- return getActiveInviteTokensByPatientUtil(this.db, patientId);
881
- }
882
-
883
- async getAestheticAnalysis(patientId: string): Promise<AestheticAnalysis | null> {
884
- const currentUser = await this.getCurrentUser();
885
- return getAestheticAnalysisUtil(this.db, patientId, currentUser.uid, currentUser.roles);
886
- }
887
-
888
- async createAestheticAnalysis(
889
- patientId: string,
890
- data: CreateAestheticAnalysisData
891
- ): Promise<void> {
892
- const currentUser = await this.getCurrentUser();
893
- return createOrUpdateAestheticAnalysisUtil(
894
- this.db,
895
- patientId,
896
- data,
897
- currentUser.uid,
898
- currentUser.roles,
899
- false
900
- );
901
- }
902
-
903
- async updateAestheticAnalysis(
904
- patientId: string,
905
- data: UpdateAestheticAnalysisData
906
- ): Promise<void> {
907
- const currentUser = await this.getCurrentUser();
908
- return createOrUpdateAestheticAnalysisUtil(
909
- this.db,
910
- patientId,
911
- data,
912
- currentUser.uid,
913
- currentUser.roles,
914
- true
915
- );
916
- }
917
-
918
- // Skin Quality Assessment methods
919
- async getSkinQualityAssessment(patientId: string): Promise<SkinQualityAssessment | null> {
920
- const currentUser = await this.getCurrentUser();
921
- return getSkinQualityAssessmentUtil(this.db, patientId, currentUser.uid, currentUser.roles);
922
- }
923
-
924
- async createSkinQualityAssessment(
925
- patientId: string,
926
- data: CreateSkinQualityAssessmentData
927
- ): Promise<void> {
928
- const currentUser = await this.getCurrentUser();
929
- return createOrUpdateSkinQualityAssessmentUtil(
930
- this.db, patientId, data, currentUser.uid, currentUser.roles, false
931
- );
932
- }
933
-
934
- async updateSkinQualityAssessment(
935
- patientId: string,
936
- data: UpdateSkinQualityAssessmentData
937
- ): Promise<void> {
938
- const currentUser = await this.getCurrentUser();
939
- return createOrUpdateSkinQualityAssessmentUtil(
940
- this.db, patientId, data, currentUser.uid, currentUser.roles, true
941
- );
942
- }
943
-
944
- // Body Assessment methods
945
- async getBodyAssessment(patientId: string): Promise<BodyAssessment | null> {
946
- const currentUser = await this.getCurrentUser();
947
- return getBodyAssessmentUtil(this.db, patientId, currentUser.uid, currentUser.roles);
948
- }
949
-
950
- async createBodyAssessment(
951
- patientId: string,
952
- data: CreateBodyAssessmentData
953
- ): Promise<void> {
954
- const currentUser = await this.getCurrentUser();
955
- return createOrUpdateBodyAssessmentUtil(
956
- this.db, patientId, data, currentUser.uid, currentUser.roles, false
957
- );
958
- }
959
-
960
- async updateBodyAssessment(
961
- patientId: string,
962
- data: UpdateBodyAssessmentData
963
- ): Promise<void> {
964
- const currentUser = await this.getCurrentUser();
965
- return createOrUpdateBodyAssessmentUtil(
966
- this.db, patientId, data, currentUser.uid, currentUser.roles, true
967
- );
968
- }
969
-
970
- // Hair & Scalp Assessment methods
971
- async getHairScalpAssessment(patientId: string): Promise<HairScalpAssessment | null> {
972
- const currentUser = await this.getCurrentUser();
973
- return getHairScalpAssessmentUtil(this.db, patientId, currentUser.uid, currentUser.roles);
974
- }
975
-
976
- async createHairScalpAssessment(
977
- patientId: string,
978
- data: CreateHairScalpAssessmentData
979
- ): Promise<void> {
980
- const currentUser = await this.getCurrentUser();
981
- return createOrUpdateHairScalpAssessmentUtil(
982
- this.db, patientId, data, currentUser.uid, currentUser.roles, false
983
- );
984
- }
985
-
986
- async updateHairScalpAssessment(
987
- patientId: string,
988
- data: UpdateHairScalpAssessmentData
989
- ): Promise<void> {
990
- const currentUser = await this.getCurrentUser();
991
- return createOrUpdateHairScalpAssessmentUtil(
992
- this.db, patientId, data, currentUser.uid, currentUser.roles, true
993
- );
994
- }
995
-
996
- // Pre-Surgical Assessment methods
997
- async getPreSurgicalAssessment(patientId: string): Promise<PreSurgicalAssessment | null> {
998
- const currentUser = await this.getCurrentUser();
999
- return getPreSurgicalAssessmentUtil(this.db, patientId, currentUser.uid, currentUser.roles);
1000
- }
1001
-
1002
- async createPreSurgicalAssessment(
1003
- patientId: string,
1004
- data: CreatePreSurgicalAssessmentData
1005
- ): Promise<void> {
1006
- const currentUser = await this.getCurrentUser();
1007
- return createOrUpdatePreSurgicalAssessmentUtil(
1008
- this.db, patientId, data, currentUser.uid, currentUser.roles, false
1009
- );
1010
- }
1011
-
1012
- async updatePreSurgicalAssessment(
1013
- patientId: string,
1014
- data: UpdatePreSurgicalAssessmentData
1015
- ): Promise<void> {
1016
- const currentUser = await this.getCurrentUser();
1017
- return createOrUpdatePreSurgicalAssessmentUtil(
1018
- this.db, patientId, data, currentUser.uid, currentUser.roles, true
1019
- );
1020
- }
1021
- }
1
+ import {
2
+ doc,
3
+ getDoc,
4
+ writeBatch,
5
+ updateDoc,
6
+ arrayUnion,
7
+ serverTimestamp,
8
+ } from 'firebase/firestore';
9
+ import { BaseService } from '../base.service';
10
+ import { MediaService, MediaAccessLevel, MediaResource } from '../media/media.service';
11
+ import {
12
+ PatientProfile,
13
+ PatientSensitiveInfo,
14
+ PatientLocationInfo,
15
+ CreatePatientProfileData,
16
+ CreatePatientSensitiveInfoData,
17
+ UpdatePatientSensitiveInfoData,
18
+ CreatePatientLocationInfoData,
19
+ UpdatePatientLocationInfoData,
20
+ PatientMedicalInfo,
21
+ CreatePatientMedicalInfoData,
22
+ UpdateVitalStatsData,
23
+ AddAllergyData,
24
+ UpdateAllergyData,
25
+ AddBlockingConditionData,
26
+ UpdateBlockingConditionData,
27
+ AddContraindicationData,
28
+ UpdateContraindicationData,
29
+ AddMedicationData,
30
+ UpdateMedicationData,
31
+ PatientDoctor,
32
+ PatientClinic,
33
+ SearchPatientsParams,
34
+ RequesterInfo,
35
+ PatientProfileForDoctor,
36
+ CreateManualPatientData,
37
+ } from '../../types/patient';
38
+ import { Auth } from 'firebase/auth';
39
+ import { Firestore } from 'firebase/firestore';
40
+ import { FirebaseApp } from 'firebase/app';
41
+ import { Timestamp } from 'firebase/firestore';
42
+
43
+ // Importujemo utility funkcije
44
+ import {
45
+ createPatientProfileUtil,
46
+ getPatientProfileUtil,
47
+ getPatientProfileByUserRefUtil,
48
+ addExpoTokenUtil,
49
+ removeExpoTokenUtil,
50
+ addPointsUtil,
51
+ updatePatientProfileUtil,
52
+ updatePatientProfileByUserRefUtil,
53
+ searchPatientsUtil,
54
+ getAllPatientsUtil,
55
+ updatePatientLocationUtil,
56
+ createLocationInfoUtil,
57
+ getLocationInfoUtil,
58
+ updateLocationInfoUtil,
59
+ createSensitiveInfoUtil,
60
+ getSensitiveInfoUtil,
61
+ updateSensitiveInfoUtil,
62
+ claimPatientSensitiveInfoUtil,
63
+ createMedicalInfoUtil,
64
+ getMedicalInfoUtil,
65
+ updateVitalStatsUtil,
66
+ addAllergyUtil,
67
+ updateAllergyUtil,
68
+ removeAllergyUtil,
69
+ addBlockingConditionUtil,
70
+ updateBlockingConditionUtil,
71
+ removeBlockingConditionUtil,
72
+ addContraindicationUtil,
73
+ updateContraindicationUtil,
74
+ removeContraindicationUtil,
75
+ addMedicationUtil,
76
+ updateMedicationUtil,
77
+ removeMedicationUtil,
78
+ getPatientDocRef,
79
+ getSensitiveInfoDocRef,
80
+ getLocationInfoDocRef,
81
+ getMedicalInfoDocRef,
82
+ addDoctorUtil,
83
+ removeDoctorUtil,
84
+ addClinicUtil,
85
+ removeClinicUtil,
86
+ getPatientsByPractitionerUtil,
87
+ getPatientsByPractitionerWithDetailsUtil,
88
+ getPatientsByClinicUtil,
89
+ getPatientsByClinicWithDetailsUtil,
90
+ createPatientTokenUtil,
91
+ validatePatientTokenUtil,
92
+ markPatientTokenAsUsedUtil,
93
+ getActiveInviteTokensByClinicUtil,
94
+ getActiveInviteTokensByPatientUtil,
95
+ } from './utils';
96
+
97
+ import {
98
+ getAestheticAnalysisUtil,
99
+ createOrUpdateAestheticAnalysisUtil,
100
+ } from './utils/aesthetic-analysis.utils';
101
+
102
+ import {
103
+ AestheticAnalysis,
104
+ CreateAestheticAnalysisData,
105
+ UpdateAestheticAnalysisData,
106
+ SkinQualityAssessment,
107
+ CreateSkinQualityAssessmentData,
108
+ UpdateSkinQualityAssessmentData,
109
+ BodyAssessment,
110
+ CreateBodyAssessmentData,
111
+ UpdateBodyAssessmentData,
112
+ HairScalpAssessment,
113
+ CreateHairScalpAssessmentData,
114
+ UpdateHairScalpAssessmentData,
115
+ PreSurgicalAssessment,
116
+ CreatePreSurgicalAssessmentData,
117
+ UpdatePreSurgicalAssessmentData,
118
+ } from '../../types/patient';
119
+
120
+ import {
121
+ getSkinQualityAssessmentUtil,
122
+ createOrUpdateSkinQualityAssessmentUtil,
123
+ } from './utils/skin-quality-assessment.utils';
124
+
125
+ import {
126
+ getBodyAssessmentUtil,
127
+ createOrUpdateBodyAssessmentUtil,
128
+ } from './utils/body-assessment.utils';
129
+
130
+ import {
131
+ getHairScalpAssessmentUtil,
132
+ createOrUpdateHairScalpAssessmentUtil,
133
+ } from './utils/hair-scalp-assessment.utils';
134
+
135
+ import {
136
+ getPreSurgicalAssessmentUtil,
137
+ createOrUpdatePreSurgicalAssessmentUtil,
138
+ } from './utils/pre-surgical-assessment.utils';
139
+
140
+ import { CreatePatientTokenData, PatientToken } from '../../types/patient/token.types';
141
+
142
+ export class PatientService extends BaseService {
143
+ private mediaService: MediaService;
144
+
145
+ constructor(db: Firestore, auth: Auth, app: FirebaseApp) {
146
+ super(db, auth, app);
147
+ this.mediaService = new MediaService(db, auth, app);
148
+ }
149
+
150
+ // Metode za rad sa profilom pacijenta
151
+ async createPatientProfile(data: CreatePatientProfileData): Promise<PatientProfile> {
152
+ return createPatientProfileUtil(this.db, data, () => this.generateId());
153
+ }
154
+
155
+ async getPatientProfile(patientId: string): Promise<PatientProfile | null> {
156
+ return getPatientProfileUtil(this.db, patientId);
157
+ }
158
+
159
+ /**
160
+ * Manually creates a new patient profile, typically initiated by a clinic admin.
161
+ * This creates a patient record that is not initially linked to an authenticated user.
162
+ *
163
+ * @param {CreateManualPatientData} data - The data for the new patient.
164
+ * @param {RequesterInfo} requester - Information about the user creating the patient (must be a clinic admin).
165
+ * @returns {Promise<PatientProfile>} The newly created patient profile.
166
+ * @throws {Error} If the requester is not a valid clinic admin.
167
+ */
168
+ async createManualPatient(
169
+ data: CreateManualPatientData,
170
+ requester: RequesterInfo,
171
+ ): Promise<PatientProfile> {
172
+ console.log(
173
+ `[PatientService.createManualPatient] Attempting to create manual patient by requester:`,
174
+ requester,
175
+ );
176
+
177
+ // Security Check: Ensure the requester is a clinic admin
178
+ if (
179
+ requester.role !== 'clinic_admin' ||
180
+ !requester.associatedClinicId ||
181
+ requester.associatedClinicId !== data.clinicId
182
+ ) {
183
+ throw new Error(
184
+ 'Unauthorized: Requester must be a clinic admin and can only add patients to their own clinic.',
185
+ );
186
+ }
187
+
188
+ const patientId = this.generateId();
189
+ const batch = writeBatch(this.db);
190
+ const now = Timestamp.now();
191
+
192
+ // 1. Create Patient Profile
193
+ const patientProfileRef = getPatientDocRef(this.db, patientId);
194
+ const newProfile: PatientProfile = {
195
+ id: patientId,
196
+ displayName: `${data.firstName} ${data.lastName.charAt(0)}.`,
197
+ expoTokens: [],
198
+ gamification: { level: 1, points: 0 },
199
+ isActive: true,
200
+ isVerified: false, // Manual profiles are not verified by default
201
+ isManual: true,
202
+ doctors: [],
203
+ clinics: [
204
+ {
205
+ clinicId: data.clinicId,
206
+ assignedAt: now,
207
+ assignedBy: requester.id,
208
+ isActive: true,
209
+ notes: data.notes,
210
+ },
211
+ ],
212
+ doctorIds: [],
213
+ clinicIds: [data.clinicId],
214
+ createdAt: now,
215
+ updatedAt: now,
216
+ phoneNumber: data.phoneNumber,
217
+ dateOfBirth: data.dateOfBirth,
218
+ };
219
+ batch.set(patientProfileRef, newProfile);
220
+
221
+ // 2. Create Patient Sensitive Info
222
+ const sensitiveInfoRef = getSensitiveInfoDocRef(this.db, patientId);
223
+ const newSensitiveInfo: Omit<PatientSensitiveInfo, 'photoUrl'> = {
224
+ patientId,
225
+ firstName: data.firstName,
226
+ lastName: data.lastName,
227
+ dateOfBirth: data.dateOfBirth,
228
+ gender: data.gender,
229
+ email: data.email,
230
+ phoneNumber: data.phoneNumber,
231
+ addressData: data.addressData,
232
+ emergencyContacts: [],
233
+ createdAt: now,
234
+ updatedAt: now,
235
+ };
236
+ batch.set(sensitiveInfoRef, newSensitiveInfo);
237
+
238
+ // 3. Create Patient Medical Info
239
+ const medicalInfoRef = getMedicalInfoDocRef(this.db, patientId);
240
+ const newMedicalInfo: PatientMedicalInfo = {
241
+ patientId,
242
+ vitalStats: {},
243
+ blockingConditions: [],
244
+ contraindications: [],
245
+ allergies: [],
246
+ currentMedications: [],
247
+ emergencyNotes: '',
248
+ lastUpdated: now,
249
+ updatedBy: requester.id, // The admin who created the record
250
+ };
251
+ batch.set(medicalInfoRef, newMedicalInfo);
252
+
253
+ await batch.commit();
254
+
255
+ console.log(
256
+ `[PatientService.createManualPatient] Successfully created manual patient with ID: ${patientId}`,
257
+ );
258
+
259
+ return newProfile;
260
+ }
261
+
262
+ async getPatientProfileByUserRef(userRef: string): Promise<PatientProfile | null> {
263
+ return getPatientProfileByUserRefUtil(this.db, userRef);
264
+ }
265
+
266
+ // Metode za rad sa lokacijskim podacima
267
+ async updatePatientLocation(
268
+ patientId: string,
269
+ latitude: number,
270
+ longitude: number,
271
+ ): Promise<void> {
272
+ await updatePatientLocationUtil(this.db, patientId, latitude, longitude);
273
+ }
274
+
275
+ async updatePatientLocationByUserRef(
276
+ userRef: string,
277
+ latitude: number,
278
+ longitude: number,
279
+ ): Promise<void> {
280
+ const profile = await this.getPatientProfileByUserRef(userRef);
281
+ if (!profile) throw new Error('Patient profile not found');
282
+ await this.updatePatientLocation(profile.id, latitude, longitude);
283
+ }
284
+
285
+ async createLocationInfo(
286
+ data: CreatePatientLocationInfoData,
287
+ requesterId: string,
288
+ ): Promise<PatientLocationInfo> {
289
+ return createLocationInfoUtil(this.db, data, requesterId);
290
+ }
291
+
292
+ async getLocationInfo(
293
+ patientId: string,
294
+ requesterId: string,
295
+ ): Promise<PatientLocationInfo | null> {
296
+ return getLocationInfoUtil(this.db, patientId, requesterId);
297
+ }
298
+
299
+ async getLocationInfoByUserRef(
300
+ userRef: string,
301
+ requesterId: string,
302
+ ): Promise<PatientLocationInfo | null> {
303
+ const profile = await this.getPatientProfileByUserRef(userRef);
304
+ if (!profile) return null;
305
+ return this.getLocationInfo(profile.id, requesterId);
306
+ }
307
+
308
+ async updateLocationInfo(
309
+ patientId: string,
310
+ data: UpdatePatientLocationInfoData,
311
+ requesterId: string,
312
+ ): Promise<PatientLocationInfo> {
313
+ return updateLocationInfoUtil(this.db, patientId, data, requesterId);
314
+ }
315
+
316
+ // Metode za rad sa osetljivim informacijama
317
+ async createSensitiveInfo(
318
+ data: CreatePatientSensitiveInfoData,
319
+ requesterUserId: string,
320
+ ): Promise<PatientSensitiveInfo> {
321
+ const currentUser = await this.getCurrentUser();
322
+ if (currentUser.uid !== requesterUserId) {
323
+ throw new Error('Requester does not match authenticated user.');
324
+ }
325
+
326
+ return createSensitiveInfoUtil(
327
+ this.db,
328
+ data,
329
+ requesterUserId,
330
+ currentUser.roles,
331
+ this.mediaService,
332
+ );
333
+ }
334
+
335
+ async getSensitiveInfo(
336
+ patientId: string,
337
+ requesterUserId: string,
338
+ ): Promise<PatientSensitiveInfo | null> {
339
+ const currentUser = await this.getCurrentUser();
340
+ if (currentUser.uid !== requesterUserId) {
341
+ // Allow for read-only access if authorized, but for now we check identity
342
+ // This could be expanded later based on practitioner/admin roles
343
+ }
344
+ return getSensitiveInfoUtil(this.db, patientId, requesterUserId, currentUser.roles);
345
+ }
346
+
347
+ async getSensitiveInfoByUserRef(
348
+ userRef: string,
349
+ requesterUserId: string,
350
+ ): Promise<PatientSensitiveInfo | null> {
351
+ const profile = await this.getPatientProfileByUserRef(userRef);
352
+ if (!profile) return null;
353
+ // We pass requesterUserId which is the UID of the one asking.
354
+ return this.getSensitiveInfo(profile.id, requesterUserId);
355
+ }
356
+
357
+ async updateSensitiveInfo(
358
+ patientId: string,
359
+ data: UpdatePatientSensitiveInfoData,
360
+ requesterUserId: string,
361
+ ): Promise<PatientSensitiveInfo> {
362
+ const currentUser = await this.getCurrentUser();
363
+ if (currentUser.uid !== requesterUserId) {
364
+ throw new Error('Requester does not match authenticated user.');
365
+ }
366
+ return updateSensitiveInfoUtil(
367
+ this.db,
368
+ patientId,
369
+ data,
370
+ requesterUserId,
371
+ currentUser.roles,
372
+ this.mediaService,
373
+ );
374
+ }
375
+
376
+ async claimPatientSensitiveInfo(
377
+ patientId: string,
378
+ userId: string,
379
+ ): Promise<PatientSensitiveInfo> {
380
+ return claimPatientSensitiveInfoUtil(this.db, patientId, userId);
381
+ }
382
+
383
+ // Metode za rad sa medicinskim informacijama
384
+ async createMedicalInfo(patientId: string, data: CreatePatientMedicalInfoData): Promise<void> {
385
+ const currentUser = await this.getCurrentUser();
386
+ await createMedicalInfoUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
387
+ }
388
+
389
+ async getMedicalInfo(patientId: string): Promise<PatientMedicalInfo> {
390
+ const currentUser = await this.getCurrentUser();
391
+ return getMedicalInfoUtil(this.db, patientId, currentUser.uid, currentUser.roles);
392
+ }
393
+
394
+ async getMedicalInfoByUserRef(userRef: string): Promise<PatientMedicalInfo> {
395
+ const profile = await this.getPatientProfileByUserRef(userRef);
396
+ if (!profile) throw new Error('Patient profile not found');
397
+ return this.getMedicalInfo(profile.id);
398
+ }
399
+
400
+ // Metode za rad sa vitalnim statistikama
401
+ async updateVitalStats(patientId: string, data: UpdateVitalStatsData): Promise<void> {
402
+ const currentUser = await this.getCurrentUser();
403
+ await updateVitalStatsUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
404
+ }
405
+
406
+ // Metode za rad sa alergijama
407
+ async addAllergy(patientId: string, data: AddAllergyData): Promise<void> {
408
+ const currentUser = await this.getCurrentUser();
409
+ await addAllergyUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
410
+ }
411
+
412
+ async updateAllergy(patientId: string, data: UpdateAllergyData): Promise<void> {
413
+ const currentUser = await this.getCurrentUser();
414
+ await updateAllergyUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
415
+ }
416
+
417
+ async removeAllergy(patientId: string, allergyIndex: number): Promise<void> {
418
+ const currentUser = await this.getCurrentUser();
419
+ await removeAllergyUtil(this.db, patientId, allergyIndex, currentUser.uid, currentUser.roles);
420
+ }
421
+
422
+ // Metode za rad sa blocking conditions
423
+ async addBlockingCondition(patientId: string, data: AddBlockingConditionData): Promise<void> {
424
+ const currentUser = await this.getCurrentUser();
425
+ await addBlockingConditionUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
426
+ }
427
+
428
+ async updateBlockingCondition(
429
+ patientId: string,
430
+ data: UpdateBlockingConditionData,
431
+ ): Promise<void> {
432
+ const currentUser = await this.getCurrentUser();
433
+ await updateBlockingConditionUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
434
+ }
435
+
436
+ async removeBlockingCondition(patientId: string, conditionIndex: number): Promise<void> {
437
+ const currentUser = await this.getCurrentUser();
438
+ await removeBlockingConditionUtil(
439
+ this.db,
440
+ patientId,
441
+ conditionIndex,
442
+ currentUser.uid,
443
+ currentUser.roles,
444
+ );
445
+ }
446
+
447
+ // Metode za rad sa kontraindikacijama
448
+ async addContraindication(patientId: string, data: AddContraindicationData): Promise<void> {
449
+ const currentUser = await this.getCurrentUser();
450
+ await addContraindicationUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
451
+ }
452
+
453
+ async updateContraindication(patientId: string, data: UpdateContraindicationData): Promise<void> {
454
+ const currentUser = await this.getCurrentUser();
455
+ await updateContraindicationUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
456
+ }
457
+
458
+ async removeContraindication(patientId: string, contraindicationIndex: number): Promise<void> {
459
+ const currentUser = await this.getCurrentUser();
460
+ await removeContraindicationUtil(
461
+ this.db,
462
+ patientId,
463
+ contraindicationIndex,
464
+ currentUser.uid,
465
+ currentUser.roles,
466
+ );
467
+ }
468
+
469
+ // Metode za rad sa medikacijama
470
+ async addMedication(patientId: string, data: AddMedicationData): Promise<void> {
471
+ const currentUser = await this.getCurrentUser();
472
+ await addMedicationUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
473
+ }
474
+
475
+ async updateMedication(patientId: string, data: UpdateMedicationData): Promise<void> {
476
+ const currentUser = await this.getCurrentUser();
477
+ await updateMedicationUtil(this.db, patientId, data, currentUser.uid, currentUser.roles);
478
+ }
479
+
480
+ async removeMedication(patientId: string, medicationIndex: number): Promise<void> {
481
+ const currentUser = await this.getCurrentUser();
482
+ await removeMedicationUtil(
483
+ this.db,
484
+ patientId,
485
+ medicationIndex,
486
+ currentUser.uid,
487
+ currentUser.roles,
488
+ );
489
+ }
490
+
491
+ // Pomoćne metode
492
+ async addExpoToken(patientId: string, token: string): Promise<void> {
493
+ await addExpoTokenUtil(this.db, patientId, token);
494
+ }
495
+
496
+ async addExpoTokenByUserRef(userRef: string, token: string): Promise<void> {
497
+ const profile = await this.getPatientProfileByUserRef(userRef);
498
+ if (!profile) throw new Error('Patient profile not found');
499
+ await this.addExpoToken(profile.id, token);
500
+ }
501
+
502
+ async removeExpoToken(patientId: string, token: string): Promise<void> {
503
+ await removeExpoTokenUtil(this.db, patientId, token);
504
+ }
505
+
506
+ async removeExpoTokenByUserRef(userRef: string, token: string): Promise<void> {
507
+ const profile = await this.getPatientProfileByUserRef(userRef);
508
+ if (!profile) throw new Error('Patient profile not found');
509
+ await this.removeExpoToken(profile.id, token);
510
+ }
511
+
512
+ async addPoints(patientId: string, points: number): Promise<void> {
513
+ await addPointsUtil(this.db, patientId, points);
514
+ }
515
+
516
+ async addPointsByUserRef(userRef: string, points: number): Promise<void> {
517
+ const profile = await this.getPatientProfileByUserRef(userRef);
518
+ if (!profile) throw new Error('Patient profile not found');
519
+ await this.addPoints(profile.id, points);
520
+ }
521
+
522
+ private async getCurrentUser(): Promise<any> {
523
+ if (!this.auth.currentUser) {
524
+ throw new Error('No authenticated user');
525
+ }
526
+ const userDoc = await getDoc(doc(this.db, 'users', this.auth.currentUser.uid));
527
+ if (!userDoc.exists()) {
528
+ throw new Error('User not found');
529
+ }
530
+ return userDoc.data();
531
+ }
532
+
533
+ /**
534
+ * Briše profil pacijenta i sve povezane subkolekcije
535
+ */
536
+ async deletePatientProfile(patientId: string): Promise<void> {
537
+ const batch = writeBatch(this.db);
538
+
539
+ // Brisanje sensitive info
540
+ batch.delete(getSensitiveInfoDocRef(this.db, patientId));
541
+
542
+ // Brisanje location info
543
+ batch.delete(getLocationInfoDocRef(this.db, patientId));
544
+
545
+ // Brisanje medical info
546
+ batch.delete(getMedicalInfoDocRef(this.db, patientId));
547
+
548
+ // Brisanje glavnog profila
549
+ batch.delete(getPatientDocRef(this.db, patientId));
550
+
551
+ await batch.commit();
552
+ }
553
+
554
+ // Metode za rad sa doktorima
555
+ async addDoctor(patientId: string, doctorRef: string): Promise<void> {
556
+ const currentUser = await this.getCurrentUser();
557
+ await addDoctorUtil(this.db, patientId, doctorRef, currentUser.uid);
558
+ }
559
+
560
+ async removeDoctor(patientId: string, doctorRef: string): Promise<void> {
561
+ await removeDoctorUtil(this.db, patientId, doctorRef);
562
+ }
563
+
564
+ // Metode za rad sa klinikama
565
+ async addClinic(patientId: string, clinicId: string): Promise<void> {
566
+ const currentUser = await this.getCurrentUser();
567
+ await addClinicUtil(this.db, patientId, clinicId, currentUser.uid);
568
+ }
569
+
570
+ async removeClinic(patientId: string, clinicId: string): Promise<void> {
571
+ await removeClinicUtil(this.db, patientId, clinicId);
572
+ }
573
+
574
+ // Metode za rad sa profilnom slikom
575
+ /**
576
+ * Uploads a profile photo for a patient
577
+ * @param patientId - ID of the patient
578
+ * @param file - File or Blob to upload
579
+ * @returns URL of the uploaded photo
580
+ */
581
+ async uploadProfilePhoto(patientId: string, file: File | Blob): Promise<string> {
582
+ console.log(`[PatientService] Uploading profile photo for patient ${patientId}`);
583
+
584
+ const mediaMetadata = await this.mediaService.uploadMedia(
585
+ file,
586
+ patientId, // Using patientId as ownerId
587
+ MediaAccessLevel.PRIVATE, // Profile photos should be private
588
+ 'patient_profile_photos',
589
+ file instanceof File ? file.name : `profile_photo_${patientId}`,
590
+ );
591
+
592
+ // Update the patient sensitive info with the new photo URL
593
+ await updateDoc(getSensitiveInfoDocRef(this.db, patientId), {
594
+ photoUrl: mediaMetadata.url,
595
+ updatedAt: serverTimestamp(),
596
+ });
597
+
598
+ return mediaMetadata.url;
599
+ }
600
+
601
+ /**
602
+ * Updates a patient's profile photo (replaces existing one)
603
+ * @param patientId - ID of the patient
604
+ * @param file - New file or Blob to upload
605
+ * @returns URL of the new uploaded photo
606
+ */
607
+ async updateProfilePhoto(patientId: string, file: File | Blob): Promise<string> {
608
+ console.log(`[PatientService] Updating profile photo for patient ${patientId}`);
609
+
610
+ // Get current patient sensitive info to check for existing photo
611
+ const currentUser = await this.getCurrentUser();
612
+ const currentSensitiveInfo = await this.getSensitiveInfo(patientId, currentUser.uid);
613
+
614
+ // Delete old photo if it exists and is managed by our MediaService
615
+ if (currentSensitiveInfo?.photoUrl && typeof currentSensitiveInfo.photoUrl === 'string') {
616
+ try {
617
+ const existingMediaMetadata = await this.mediaService.getMediaMetadataByUrl(
618
+ currentSensitiveInfo.photoUrl,
619
+ );
620
+ if (existingMediaMetadata) {
621
+ await this.mediaService.deleteMedia(existingMediaMetadata.id);
622
+ }
623
+ } catch (error) {
624
+ console.warn(
625
+ `[PatientService] Could not delete old profile photo for patient ${patientId}:`,
626
+ error,
627
+ );
628
+ // Continue with upload even if deletion fails
629
+ }
630
+ }
631
+
632
+ // Upload new photo
633
+ return this.uploadProfilePhoto(patientId, file);
634
+ }
635
+
636
+ /**
637
+ * Deletes a patient's profile photo
638
+ * @param patientId - ID of the patient
639
+ */
640
+ async deleteProfilePhoto(patientId: string): Promise<void> {
641
+ console.log(`[PatientService] Deleting profile photo for patient ${patientId}`);
642
+
643
+ // Get current patient sensitive info to find the photo URL
644
+ const currentUser = await this.getCurrentUser();
645
+ const currentSensitiveInfo = await this.getSensitiveInfo(patientId, currentUser.uid);
646
+
647
+ if (currentSensitiveInfo?.photoUrl && typeof currentSensitiveInfo.photoUrl === 'string') {
648
+ try {
649
+ const existingMediaMetadata = await this.mediaService.getMediaMetadataByUrl(
650
+ currentSensitiveInfo.photoUrl,
651
+ );
652
+ if (existingMediaMetadata) {
653
+ await this.mediaService.deleteMedia(existingMediaMetadata.id);
654
+ }
655
+ } catch (error) {
656
+ console.warn(
657
+ `[PatientService] Could not delete profile photo for patient ${patientId}:`,
658
+ error,
659
+ );
660
+ }
661
+
662
+ // Remove photo URL from patient sensitive info
663
+ await updateDoc(getSensitiveInfoDocRef(this.db, patientId), {
664
+ photoUrl: null,
665
+ updatedAt: serverTimestamp(),
666
+ });
667
+ }
668
+ }
669
+
670
+ /**
671
+ * Handles profile photo upload for patients (supports MediaResource)
672
+ * @param photoUrl - MediaResource (File, Blob, or URL string) from CreatePatientSensitiveInfoData
673
+ * @param patientId - ID of the patient
674
+ * @returns URL string of the uploaded or existing photo
675
+ */
676
+ private async handleProfilePhotoUpload(
677
+ photoUrl: MediaResource | undefined,
678
+ patientId: string,
679
+ ): Promise<string | undefined> {
680
+ if (!photoUrl) {
681
+ return undefined;
682
+ }
683
+
684
+ // If it's already a URL string, return it as is
685
+ if (typeof photoUrl === 'string') {
686
+ return photoUrl;
687
+ }
688
+
689
+ // If it's a File or Blob, upload it
690
+ if (photoUrl instanceof File || photoUrl instanceof Blob) {
691
+ return this.uploadProfilePhoto(patientId, photoUrl);
692
+ }
693
+
694
+ return undefined;
695
+ }
696
+
697
+ // Metode za ažuriranje profila
698
+ async updatePatientProfile(
699
+ patientId: string,
700
+ data: Partial<Omit<PatientProfile, 'id' | 'createdAt' | 'updatedAt'>>,
701
+ ): Promise<PatientProfile> {
702
+ return updatePatientProfileUtil(this.db, patientId, data);
703
+ }
704
+
705
+ async updatePatientProfileByUserRef(
706
+ userRef: string,
707
+ data: Partial<Omit<PatientProfile, 'id' | 'createdAt' | 'updatedAt'>>,
708
+ ): Promise<PatientProfile> {
709
+ return updatePatientProfileByUserRefUtil(this.db, userRef, data);
710
+ }
711
+
712
+ /**
713
+ * Searches for patient profiles based on clinic/practitioner association.
714
+ * Requires information about the requester for security checks.
715
+ *
716
+ * @param {SearchPatientsParams} params - The search criteria (clinicId, practitionerId).
717
+ * @param {RequesterInfo} requester - Information about the user performing the search (ID, role, associated IDs).
718
+ * @returns {Promise<PatientProfile[]>} A promise resolving to an array of matching patient profiles.
719
+ */
720
+ async searchPatients(
721
+ params: SearchPatientsParams,
722
+ requester: RequesterInfo,
723
+ ): Promise<PatientProfile[]> {
724
+ // We can potentially add more service-level logic here in the future,
725
+ // like fetching additional data or enriching the results.
726
+ // For now, we delegate directly to the utility function.
727
+ console.log(
728
+ `[PatientService.searchPatients] Initiating search with params:`,
729
+ params,
730
+ `by requester:`,
731
+ requester,
732
+ );
733
+
734
+ // The utility function already handles validation and security checks.
735
+ return searchPatientsUtil(this.db, params, requester);
736
+ }
737
+
738
+ /**
739
+ * Gets all patient profiles.
740
+ *
741
+ * @param {Object} options - Optional parameters for pagination
742
+ * @param {number} options.limit - Maximum number of profiles to return
743
+ * @param {string} options.startAfter - The ID of the document to start after (for pagination)
744
+ * @returns {Promise<PatientProfile[]>} A promise resolving to an array of all patient profiles.
745
+ */
746
+ async getAllPatients(options?: {
747
+ limit?: number;
748
+ startAfter?: string;
749
+ }): Promise<PatientProfile[]> {
750
+ console.log(`[PatientService.getAllPatients] Fetching patients with options:`, options);
751
+ return getAllPatientsUtil(this.db, options);
752
+ }
753
+
754
+ /**
755
+ * Gets all patients associated with a specific practitioner.
756
+ *
757
+ * @param {string} practitionerId - ID of the practitioner whose patients to retrieve
758
+ * @param {Object} options - Optional parameters for pagination
759
+ * @param {number} options.limit - Maximum number of profiles to return
760
+ * @param {string} options.startAfter - The ID of the document to start after (for pagination)
761
+ * @returns {Promise<PatientProfile[]>} A promise resolving to an array of patient profiles
762
+ */
763
+ async getPatientsByPractitioner(
764
+ practitionerId: string,
765
+ options?: {
766
+ limit?: number;
767
+ startAfter?: string;
768
+ },
769
+ ): Promise<PatientProfile[]> {
770
+ console.log(
771
+ `[PatientService.getPatientsByPractitioner] Fetching patients for practitioner: ${practitionerId}`,
772
+ );
773
+ return getPatientsByPractitionerUtil(this.db, practitionerId, options);
774
+ }
775
+
776
+ /**
777
+ * Gets all patients associated with a specific practitioner with their sensitive information.
778
+ *
779
+ * @param {string} practitionerId - ID of the practitioner whose patients to retrieve
780
+ * @param {Object} options - Optional parameters for pagination
781
+ * @param {number} options.limit - Maximum number of profiles to return
782
+ * @param {string} options.startAfter - The ID of the document to start after (for pagination)
783
+ * @returns {Promise<PatientProfileForDoctor[]>} A promise resolving to an array of patient profiles with sensitive info
784
+ */
785
+ async getPatientsByPractitionerWithDetails(
786
+ practitionerId: string,
787
+ options?: {
788
+ limit?: number;
789
+ startAfter?: string;
790
+ },
791
+ ): Promise<PatientProfileForDoctor[]> {
792
+ console.log(
793
+ `[PatientService.getPatientsByPractitionerWithDetails] Fetching detailed patient profiles for practitioner: ${practitionerId}`,
794
+ );
795
+ return getPatientsByPractitionerWithDetailsUtil(this.db, practitionerId, options);
796
+ }
797
+
798
+ /**
799
+ * Gets all patients associated with a specific clinic, including sensitive info.
800
+ * This merges data from PatientProfile and PatientSensitiveInfo subcollection.
801
+ *
802
+ * @param {string} clinicId - ID of the clinic whose patients to retrieve
803
+ * @param {Object} options - Optional parameters for pagination
804
+ * @param {number} options.limit - Maximum number of profiles to return
805
+ * @param {string} options.startAfter - The ID of the document to start after (for pagination)
806
+ * @returns {Promise<PatientProfile[]>} A promise resolving to an array of patient profiles with merged sensitive info
807
+ */
808
+ async getPatientsByClinic(
809
+ clinicId: string,
810
+ options?: {
811
+ limit?: number;
812
+ startAfter?: string;
813
+ },
814
+ ): Promise<PatientProfile[]> {
815
+ console.log(`[PatientService.getPatientsByClinic] Fetching patients with details for clinic: ${clinicId}`);
816
+ return getPatientsByClinicWithDetailsUtil(this.db, clinicId, options);
817
+ }
818
+
819
+ /**
820
+ * Creates a token for inviting a patient to claim their profile.
821
+ *
822
+ * @param {CreatePatientTokenData} data - Data for creating the token.
823
+ * @param {string} createdBy - ID of the admin user creating the token.
824
+ * @returns {Promise<PatientToken>} The created token.
825
+ */
826
+ async createPatientToken(data: CreatePatientTokenData, createdBy: string): Promise<PatientToken> {
827
+ // We assume the 'createdBy' user is validated to be a clinic admin
828
+ // in the calling context (e.g., a cloud function or API endpoint).
829
+ return createPatientTokenUtil(
830
+ this.db,
831
+ data,
832
+ createdBy,
833
+ () => this.generateId(), // Pass the ID generation function
834
+ );
835
+ }
836
+
837
+ /**
838
+ * Validates a patient invitation token.
839
+ *
840
+ * @param {string} tokenString - The token string to validate.
841
+ * @returns {Promise<PatientToken | null>} The token if found and valid, otherwise null.
842
+ */
843
+ async validatePatientToken(tokenString: string): Promise<PatientToken | null> {
844
+ return validatePatientTokenUtil(this.db, tokenString);
845
+ }
846
+
847
+ /**
848
+ * Marks a patient invitation token as used.
849
+ *
850
+ * @param {string} tokenId - The ID of the token to mark as used.
851
+ * @param {string} patientId - The ID of the patient associated with the token.
852
+ * @param {string} userId - The ID of the user who is using the token.
853
+ * @returns {Promise<void>}
854
+ */
855
+ async markPatientTokenAsUsed(tokenId: string, patientId: string, userId: string): Promise<void> {
856
+ return markPatientTokenAsUsedUtil(this.db, tokenId, patientId, userId);
857
+ }
858
+
859
+ /**
860
+ * Retrieves all active invitation tokens for a specific clinic.
861
+ * NOTE: This should be protected and only exposed to authorized clinic admins.
862
+ *
863
+ * @param {string} clinicId - The ID of the clinic.
864
+ * @returns {Promise<PatientToken[]>} An array of active tokens for the clinic.
865
+ */
866
+ async getActiveInviteTokensByClinic(clinicId: string): Promise<PatientToken[]> {
867
+ return getActiveInviteTokensByClinicUtil(this.db, clinicId);
868
+ }
869
+
870
+ /**
871
+ * Retrieves all active invitation tokens for a specific patient.
872
+ * NOTE: This should be protected and only exposed to authorized clinic admins.
873
+ *
874
+ * @param {string} patientId - The ID of the patient.
875
+ * @returns {Promise<PatientToken[]>} An array of active tokens for the patient.
876
+ */
877
+ async getActiveInviteTokensByPatient(patientId: string): Promise<PatientToken[]> {
878
+ // Security check should be done in the calling context to ensure
879
+ // the admin has permission to view this patient's tokens.
880
+ return getActiveInviteTokensByPatientUtil(this.db, patientId);
881
+ }
882
+
883
+ async getAestheticAnalysis(patientId: string): Promise<AestheticAnalysis | null> {
884
+ const currentUser = await this.getCurrentUser();
885
+ return getAestheticAnalysisUtil(this.db, patientId, currentUser.uid, currentUser.roles);
886
+ }
887
+
888
+ async createAestheticAnalysis(
889
+ patientId: string,
890
+ data: CreateAestheticAnalysisData
891
+ ): Promise<void> {
892
+ const currentUser = await this.getCurrentUser();
893
+ return createOrUpdateAestheticAnalysisUtil(
894
+ this.db,
895
+ patientId,
896
+ data,
897
+ currentUser.uid,
898
+ currentUser.roles,
899
+ false
900
+ );
901
+ }
902
+
903
+ async updateAestheticAnalysis(
904
+ patientId: string,
905
+ data: UpdateAestheticAnalysisData
906
+ ): Promise<void> {
907
+ const currentUser = await this.getCurrentUser();
908
+ return createOrUpdateAestheticAnalysisUtil(
909
+ this.db,
910
+ patientId,
911
+ data,
912
+ currentUser.uid,
913
+ currentUser.roles,
914
+ true
915
+ );
916
+ }
917
+
918
+ // Skin Quality Assessment methods
919
+ async getSkinQualityAssessment(patientId: string): Promise<SkinQualityAssessment | null> {
920
+ const currentUser = await this.getCurrentUser();
921
+ return getSkinQualityAssessmentUtil(this.db, patientId, currentUser.uid, currentUser.roles);
922
+ }
923
+
924
+ async createSkinQualityAssessment(
925
+ patientId: string,
926
+ data: CreateSkinQualityAssessmentData
927
+ ): Promise<void> {
928
+ const currentUser = await this.getCurrentUser();
929
+ return createOrUpdateSkinQualityAssessmentUtil(
930
+ this.db, patientId, data, currentUser.uid, currentUser.roles, false
931
+ );
932
+ }
933
+
934
+ async updateSkinQualityAssessment(
935
+ patientId: string,
936
+ data: UpdateSkinQualityAssessmentData
937
+ ): Promise<void> {
938
+ const currentUser = await this.getCurrentUser();
939
+ return createOrUpdateSkinQualityAssessmentUtil(
940
+ this.db, patientId, data, currentUser.uid, currentUser.roles, true
941
+ );
942
+ }
943
+
944
+ // Body Assessment methods
945
+ async getBodyAssessment(patientId: string): Promise<BodyAssessment | null> {
946
+ const currentUser = await this.getCurrentUser();
947
+ return getBodyAssessmentUtil(this.db, patientId, currentUser.uid, currentUser.roles);
948
+ }
949
+
950
+ async createBodyAssessment(
951
+ patientId: string,
952
+ data: CreateBodyAssessmentData
953
+ ): Promise<void> {
954
+ const currentUser = await this.getCurrentUser();
955
+ return createOrUpdateBodyAssessmentUtil(
956
+ this.db, patientId, data, currentUser.uid, currentUser.roles, false
957
+ );
958
+ }
959
+
960
+ async updateBodyAssessment(
961
+ patientId: string,
962
+ data: UpdateBodyAssessmentData
963
+ ): Promise<void> {
964
+ const currentUser = await this.getCurrentUser();
965
+ return createOrUpdateBodyAssessmentUtil(
966
+ this.db, patientId, data, currentUser.uid, currentUser.roles, true
967
+ );
968
+ }
969
+
970
+ // Hair & Scalp Assessment methods
971
+ async getHairScalpAssessment(patientId: string): Promise<HairScalpAssessment | null> {
972
+ const currentUser = await this.getCurrentUser();
973
+ return getHairScalpAssessmentUtil(this.db, patientId, currentUser.uid, currentUser.roles);
974
+ }
975
+
976
+ async createHairScalpAssessment(
977
+ patientId: string,
978
+ data: CreateHairScalpAssessmentData
979
+ ): Promise<void> {
980
+ const currentUser = await this.getCurrentUser();
981
+ return createOrUpdateHairScalpAssessmentUtil(
982
+ this.db, patientId, data, currentUser.uid, currentUser.roles, false
983
+ );
984
+ }
985
+
986
+ async updateHairScalpAssessment(
987
+ patientId: string,
988
+ data: UpdateHairScalpAssessmentData
989
+ ): Promise<void> {
990
+ const currentUser = await this.getCurrentUser();
991
+ return createOrUpdateHairScalpAssessmentUtil(
992
+ this.db, patientId, data, currentUser.uid, currentUser.roles, true
993
+ );
994
+ }
995
+
996
+ // Pre-Surgical Assessment methods
997
+ async getPreSurgicalAssessment(patientId: string): Promise<PreSurgicalAssessment | null> {
998
+ const currentUser = await this.getCurrentUser();
999
+ return getPreSurgicalAssessmentUtil(this.db, patientId, currentUser.uid, currentUser.roles);
1000
+ }
1001
+
1002
+ async createPreSurgicalAssessment(
1003
+ patientId: string,
1004
+ data: CreatePreSurgicalAssessmentData
1005
+ ): Promise<void> {
1006
+ const currentUser = await this.getCurrentUser();
1007
+ return createOrUpdatePreSurgicalAssessmentUtil(
1008
+ this.db, patientId, data, currentUser.uid, currentUser.roles, false
1009
+ );
1010
+ }
1011
+
1012
+ async updatePreSurgicalAssessment(
1013
+ patientId: string,
1014
+ data: UpdatePreSurgicalAssessmentData
1015
+ ): Promise<void> {
1016
+ const currentUser = await this.getCurrentUser();
1017
+ return createOrUpdatePreSurgicalAssessmentUtil(
1018
+ this.db, patientId, data, currentUser.uid, currentUser.roles, true
1019
+ );
1020
+ }
1021
+ }