@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,597 +1,597 @@
1
- import {
2
- collection,
3
- doc,
4
- getDoc,
5
- getDocs,
6
- setDoc,
7
- updateDoc,
8
- query,
9
- where,
10
- orderBy,
11
- limit,
12
- startAfter,
13
- QueryDocumentSnapshot,
14
- } from "firebase/firestore";
15
- import { BaseService } from "../base.service";
16
- import {
17
- // DocumentTemplate, // Not directly used in this file if relying on templateService
18
- // DOCUMENTATION_TEMPLATES_COLLECTION, // No longer needed directly here if using templateService
19
- // FILLED_DOCUMENTS_COLLECTION, // This will be replaced by subcollection paths
20
- FilledDocument,
21
- FilledDocumentStatus,
22
- FilledDocumentFileValue,
23
- USER_FORMS_SUBCOLLECTION,
24
- DOCTOR_FORMS_SUBCOLLECTION,
25
- } from "../../types"; // General types
26
- import { APPOINTMENTS_COLLECTION } from "../../types/appointment"; // Specific import for the constant
27
- import { DocumentationTemplateService } from "./documentation-template.service";
28
- import {
29
- MediaService,
30
- MediaAccessLevel,
31
- MediaMetadata,
32
- } from "../media/media.service";
33
- // Import the new validation schemas if you plan to use them for input validation here
34
- // import { createFilledDocumentDataSchema, updateFilledDocumentDataSchema } from '../../validations/documentation-templates.schema';
35
-
36
- /**
37
- * Service for managing filled documents within appointment subcollections
38
- */
39
- export class FilledDocumentService extends BaseService {
40
- // No single collectionRef anymore, paths will be dynamic
41
- private readonly templateService: DocumentationTemplateService;
42
- private readonly mediaService: MediaService;
43
-
44
- constructor(...args: ConstructorParameters<typeof BaseService>) {
45
- super(...args);
46
- this.templateService = new DocumentationTemplateService(...args); // Pass db and other args
47
- this.mediaService = new MediaService(...args); // Initialize media service with the same args
48
- }
49
-
50
- private getFormSubcollectionPath(
51
- isUserForm: boolean
52
- ): typeof USER_FORMS_SUBCOLLECTION | typeof DOCTOR_FORMS_SUBCOLLECTION {
53
- return isUserForm ? USER_FORMS_SUBCOLLECTION : DOCTOR_FORMS_SUBCOLLECTION;
54
- }
55
-
56
- /**
57
- * Create a new filled document within an appointment's subcollection.
58
- * @param templateId - ID of the template to use.
59
- * @param templateVersion - Version of the template to use.
60
- * @param appointmentId - ID of the appointment this form belongs to.
61
- * @param procedureId - ID of the procedure associated with this form.
62
- * @param patientId - ID of the patient.
63
- * @param practitionerId - ID of the practitioner (can be system/generic if patient is filling).
64
- * @param clinicId - ID of the clinic.
65
- * @param initialValues - Optional initial values for the form elements.
66
- * @param initialStatus - Optional initial status for the form.
67
- * @returns The created filled document.
68
- */
69
- async createFilledDocumentForAppointment(
70
- templateId: string,
71
- templateVersion: number,
72
- appointmentId: string,
73
- procedureId: string,
74
- patientId: string,
75
- practitionerId: string, // Consider if this is always available or if a placeholder is needed
76
- clinicId: string, // Same consideration as practitionerId
77
- initialValues: { [elementId: string]: any } = {},
78
- initialStatus: FilledDocumentStatus = FilledDocumentStatus.DRAFT
79
- ): Promise<FilledDocument> {
80
- const template = await this.templateService.getTemplateById(
81
- templateId,
82
- templateVersion
83
- );
84
- if (!template) {
85
- throw new Error(`Template with ID ${templateId} not found`);
86
- }
87
-
88
- const documentId = this.generateId();
89
- const now = Date.now();
90
- const isUserForm = template.isUserForm || false;
91
- const formSubcollection = this.getFormSubcollectionPath(isUserForm);
92
-
93
- const filledDocument: FilledDocument = {
94
- id: documentId,
95
- templateId,
96
- templateVersion: template.version,
97
- isUserForm: isUserForm, // Set based on template
98
- isRequired: template.isRequired || false, // Inherit isRequired from the template
99
- appointmentId: appointmentId, // NEW
100
- procedureId: procedureId, // NEW
101
- patientId,
102
- practitionerId,
103
- clinicId,
104
- createdAt: now,
105
- updatedAt: now,
106
- values: initialValues,
107
- status: initialStatus,
108
- };
109
-
110
- const docRef = doc(
111
- this.db,
112
- APPOINTMENTS_COLLECTION, // Replaced "appointments"
113
- appointmentId,
114
- formSubcollection,
115
- documentId
116
- );
117
- await setDoc(docRef, filledDocument);
118
-
119
- return filledDocument;
120
- }
121
-
122
- /**
123
- * Get a specific filled document from an appointment's subcollection.
124
- * @param appointmentId - ID of the appointment.
125
- * @param formId - ID of the filled document.
126
- * @param isUserForm - Boolean indicating if it's a user form or doctor form.
127
- * @returns The filled document or null if not found.
128
- */
129
- async getFilledDocumentFromAppointmentById(
130
- appointmentId: string,
131
- formId: string,
132
- isUserForm: boolean
133
- ): Promise<FilledDocument | null> {
134
- const formSubcollection = this.getFormSubcollectionPath(isUserForm);
135
- const docRef = doc(
136
- this.db,
137
- APPOINTMENTS_COLLECTION, // Replaced "appointments"
138
- appointmentId,
139
- formSubcollection,
140
- formId
141
- );
142
- const docSnap = await getDoc(docRef);
143
-
144
- if (!docSnap.exists()) {
145
- return null;
146
- }
147
- const data = docSnap.data() as FilledDocument;
148
- // Ensure id is populated from Firestore document ID if not in data
149
- if (!data.id) {
150
- data.id = docSnap.id;
151
- }
152
- return data;
153
- }
154
-
155
- /**
156
- * Update values or status in a filled document within an appointment's subcollection.
157
- * @param appointmentId - ID of the appointment.
158
- * @param formId - ID of the filled document to update.
159
- * @param isUserForm - Boolean indicating if it's a user form or doctor form.
160
- * @param values - Updated values for elements.
161
- * @param status - Optional new status for the document.
162
- * @returns The updated filled document.
163
- */
164
- async updateFilledDocumentInAppointment(
165
- appointmentId: string,
166
- formId: string,
167
- isUserForm: boolean,
168
- values?: { [elementId: string]: any },
169
- status?: FilledDocumentStatus
170
- ): Promise<FilledDocument> {
171
- const formSubcollection = this.getFormSubcollectionPath(isUserForm);
172
- const docRef = doc(
173
- this.db,
174
- APPOINTMENTS_COLLECTION, // Replaced "appointments"
175
- appointmentId,
176
- formSubcollection,
177
- formId
178
- );
179
-
180
- const existingDoc = await this.getFilledDocumentFromAppointmentById(
181
- appointmentId,
182
- formId,
183
- isUserForm
184
- );
185
-
186
- if (!existingDoc) {
187
- throw new Error(
188
- `Filled document with ID ${formId} not found in appointment ${appointmentId} ${formSubcollection}`
189
- );
190
- }
191
-
192
- const updatePayload: Partial<FilledDocument> = {
193
- updatedAt: Date.now(),
194
- };
195
-
196
- if (values) {
197
- updatePayload.values = {
198
- ...existingDoc.values,
199
- ...values,
200
- };
201
- }
202
- if (status) {
203
- updatePayload.status = status;
204
- }
205
-
206
- if (
207
- Object.keys(updatePayload).length === 1 &&
208
- "updatedAt" in updatePayload
209
- ) {
210
- // Only updatedAt, no actual data change, so we can skip the update
211
- // or just update timestamp if that is the desired behavior.
212
- // For now, we proceed to update the timestamp at least.
213
- }
214
-
215
- await updateDoc(docRef, updatePayload);
216
-
217
- return { ...existingDoc, ...updatePayload } as FilledDocument;
218
- }
219
-
220
- /**
221
- * Get all filled user forms for a specific appointment.
222
- * @param appointmentId ID of the appointment.
223
- * @param pageSize Number of documents to retrieve.
224
- * @param lastDoc Last document from previous page for pagination.
225
- */
226
- async getFilledUserFormsForAppointment(
227
- appointmentId: string,
228
- pageSize = 20,
229
- lastDoc?: QueryDocumentSnapshot<FilledDocument>
230
- ): Promise<{
231
- documents: FilledDocument[];
232
- lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
233
- }> {
234
- const subcollectionRef = collection(
235
- this.db,
236
- APPOINTMENTS_COLLECTION, // Replaced "appointments"
237
- appointmentId,
238
- USER_FORMS_SUBCOLLECTION
239
- );
240
- let q = query(
241
- subcollectionRef,
242
- orderBy("updatedAt", "desc"),
243
- limit(pageSize)
244
- );
245
-
246
- if (lastDoc) {
247
- q = query(q, startAfter(lastDoc));
248
- }
249
- return this.executeQuery(q);
250
- }
251
-
252
- /**
253
- * Get all filled doctor forms for a specific appointment.
254
- * @param appointmentId ID of the appointment.
255
- * @param pageSize Number of documents to retrieve.
256
- * @param lastDoc Last document from previous page for pagination.
257
- */
258
- async getFilledDoctorFormsForAppointment(
259
- appointmentId: string,
260
- pageSize = 20,
261
- lastDoc?: QueryDocumentSnapshot<FilledDocument>
262
- ): Promise<{
263
- documents: FilledDocument[];
264
- lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
265
- }> {
266
- const subcollectionRef = collection(
267
- this.db,
268
- APPOINTMENTS_COLLECTION, // Replaced "appointments"
269
- appointmentId,
270
- DOCTOR_FORMS_SUBCOLLECTION
271
- );
272
- let q = query(
273
- subcollectionRef,
274
- orderBy("updatedAt", "desc"),
275
- limit(pageSize)
276
- );
277
-
278
- if (lastDoc) {
279
- q = query(q, startAfter(lastDoc));
280
- }
281
- return this.executeQuery(q);
282
- }
283
-
284
- // Helper to execute query and return documents + lastDoc
285
- private async executeQuery(
286
- q: any // Firestore Query type
287
- ): Promise<{
288
- documents: FilledDocument[];
289
- lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
290
- }> {
291
- const querySnapshot = await getDocs(q);
292
- const documents: FilledDocument[] = [];
293
- let lastVisible: QueryDocumentSnapshot<FilledDocument> | null = null;
294
-
295
- querySnapshot.forEach((docSnapshot) => {
296
- const data = docSnapshot.data() as FilledDocument;
297
- // Ensure id is populated from Firestore document ID if not in data
298
- if (!data.id) {
299
- data.id = docSnapshot.id;
300
- }
301
- documents.push(data);
302
- lastVisible = docSnapshot as QueryDocumentSnapshot<FilledDocument>;
303
- });
304
-
305
- return {
306
- documents,
307
- lastDoc: lastVisible,
308
- };
309
- }
310
-
311
- // IMPORTANT: The following methods that query across all patients/practitioners/clinics
312
- // (e.g., getFilledDocumentsByPatient, getFilledDocumentsByPractitioner)
313
- // will NOT work correctly if FILLED_DOCUMENTS_COLLECTION is no longer a top-level collection
314
- // and data is only in appointment subcollections. You would need to use
315
- // Firestore Collection Group Queries for that, which require an index and a different query approach.
316
- // These methods are left here for now but would need significant rework or removal.
317
-
318
- /**
319
- * Get filled documents for a patient (NEEDS REWORK for subcollections or Collection Group Query)
320
- * @param patientId - ID of the patient
321
- * @param pageSize - Number of documents to retrieve
322
- * @param lastDoc - Last document from previous page for pagination
323
- * @returns Array of filled documents and the last document for pagination
324
- */
325
- async getFilledDocumentsByPatient(
326
- patientId: string,
327
- pageSize = 20,
328
- lastDoc?: QueryDocumentSnapshot<FilledDocument>
329
- ): Promise<{
330
- documents: FilledDocument[];
331
- lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
332
- }> {
333
- console.warn(
334
- "getFilledDocumentsByPatient needs rework for subcollection model or Collection Group Queries."
335
- );
336
- // Original logic commented out as this.collectionRef is no longer valid for this model
337
- /*
338
- let q = query(
339
- this.collectionRef, // LINTER ERROR: this.collectionRef no longer exists
340
- where("patientId", "==", patientId),
341
- orderBy("updatedAt", "desc"),
342
- limit(pageSize)
343
- );
344
-
345
- if (lastDoc) {
346
- q = query(q, startAfter(lastDoc));
347
- }
348
-
349
- const querySnapshot = await getDocs(q);
350
- const documents: FilledDocument[] = [];
351
- let lastVisible: QueryDocumentSnapshot<FilledDocument> | null = null;
352
-
353
- querySnapshot.forEach((doc) => {
354
- documents.push(doc.data() as FilledDocument);
355
- lastVisible = doc as QueryDocumentSnapshot<FilledDocument>;
356
- });
357
-
358
- return {
359
- documents,
360
- lastDoc: lastVisible,
361
- };
362
- */
363
- return { documents: [], lastDoc: null }; // Placeholder return
364
- }
365
-
366
- /**
367
- * Get filled documents for a practitioner (NEEDS REWORK for subcollections or Collection Group Query)
368
- * @param practitionerId - ID of the practitioner
369
- * @param pageSize - Number of documents to retrieve
370
- * @param lastDoc - Last document from previous page for pagination
371
- * @returns Array of filled documents and the last document for pagination
372
- */
373
- async getFilledDocumentsByPractitioner(
374
- practitionerId: string,
375
- pageSize = 20,
376
- lastDoc?: QueryDocumentSnapshot<FilledDocument>
377
- ): Promise<{
378
- documents: FilledDocument[];
379
- lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
380
- }> {
381
- console.warn(
382
- "getFilledDocumentsByPractitioner needs rework for subcollection model or Collection Group Queries."
383
- );
384
- // Original logic commented out
385
- /*
386
- let q = query(
387
- this.collectionRef, // LINTER ERROR
388
- where("practitionerId", "==", practitionerId),
389
- orderBy("updatedAt", "desc"),
390
- limit(pageSize)
391
- );
392
- // ... rest of original logic ...
393
- */
394
- return { documents: [], lastDoc: null }; // Placeholder return
395
- }
396
-
397
- /**
398
- * Get filled documents for a clinic (NEEDS REWORK for subcollections or Collection Group Query)
399
- * @param clinicId - ID of the clinic
400
- * @param pageSize - Number of documents to retrieve
401
- * @param lastDoc - Last document from previous page for pagination
402
- * @returns Array of filled documents and the last document for pagination
403
- */
404
- async getFilledDocumentsByClinic(
405
- clinicId: string,
406
- pageSize = 20,
407
- lastDoc?: QueryDocumentSnapshot<FilledDocument>
408
- ): Promise<{
409
- documents: FilledDocument[];
410
- lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
411
- }> {
412
- console.warn(
413
- "getFilledDocumentsByClinic needs rework for subcollection model or Collection Group Queries."
414
- );
415
- // Original logic commented out
416
- /*
417
- let q = query(
418
- this.collectionRef, // LINTER ERROR
419
- where("clinicId", "==", clinicId),
420
- orderBy("updatedAt", "desc"),
421
- limit(pageSize)
422
- );
423
- // ... rest of original logic ...
424
- */
425
- return { documents: [], lastDoc: null }; // Placeholder return
426
- }
427
-
428
- /**
429
- * Get filled documents by template (NEEDS REWORK for subcollections or Collection Group Query)
430
- * @param templateId - ID of the template
431
- * @param pageSize - Number of documents to retrieve
432
- * @param lastDoc - Last document from previous page for pagination
433
- * @returns Array of filled documents and the last document for pagination
434
- */
435
- async getFilledDocumentsByTemplate(
436
- templateId: string,
437
- pageSize = 20,
438
- lastDoc?: QueryDocumentSnapshot<FilledDocument>
439
- ): Promise<{
440
- documents: FilledDocument[];
441
- lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
442
- }> {
443
- console.warn(
444
- "getFilledDocumentsByTemplate needs rework for subcollection model or Collection Group Queries."
445
- );
446
- // Original logic commented out
447
- /*
448
- let q = query(
449
- this.collectionRef, // LINTER ERROR
450
- where("templateId", "==", templateId),
451
- orderBy("updatedAt", "desc"),
452
- limit(pageSize)
453
- );
454
- // ... rest of original logic ...
455
- */
456
- return { documents: [], lastDoc: null }; // Placeholder return
457
- }
458
-
459
- /**
460
- * Get filled documents by status (NEEDS REWORK for subcollections or Collection Group Query)
461
- * @param status - Status to filter by
462
- * @param pageSize - Number of documents to retrieve
463
- * @param lastDoc - Last document from previous page for pagination
464
- * @returns Array of filled documents and the last document for pagination
465
- */
466
- async getFilledDocumentsByStatus(
467
- status: FilledDocumentStatus,
468
- pageSize = 20,
469
- lastDoc?: QueryDocumentSnapshot<FilledDocument>
470
- ): Promise<{
471
- documents: FilledDocument[];
472
- lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
473
- }> {
474
- console.warn(
475
- "getFilledDocumentsByStatus needs rework for subcollection model or Collection Group Queries."
476
- );
477
- // Original logic commented out
478
- /*
479
- let q = query(
480
- this.collectionRef, // LINTER ERROR
481
- where("status", "==", status),
482
- orderBy("updatedAt", "desc"),
483
- limit(pageSize)
484
- );
485
- // ... rest of original logic ...
486
- */
487
- return { documents: [], lastDoc: null }; // Placeholder return
488
- }
489
-
490
- /**
491
- * Upload a file for a filled document field without updating the document.
492
- * This method only handles the upload and returns the file value to be used by the UI.
493
- *
494
- * @param appointmentId - ID of the appointment
495
- * @param formId - ID of the filled document
496
- * @param isUserForm - Boolean indicating if it's a user form or doctor form
497
- * @param file - The file to upload
498
- * @returns The file value object to be stored in the document
499
- */
500
- async uploadFileForFilledDocument(
501
- appointmentId: string,
502
- formId: string,
503
- isUserForm: boolean,
504
- file: File | Blob
505
- ): Promise<FilledDocumentFileValue> {
506
- console.log(
507
- `[FilledDocumentService] Uploading file for form ${formId} in appointment ${appointmentId}`
508
- );
509
-
510
- // Generate a unique file ID
511
- const fileId = this.generateId();
512
-
513
- // Set the path according to the specified structure
514
- const formType = isUserForm ? "user-form" : "doctor-form";
515
- const collectionName = `${formType}/${formId}`;
516
-
517
- // Always use CONFIDENTIAL access level
518
- const accessLevel = MediaAccessLevel.CONFIDENTIAL;
519
-
520
- // Upload the file using MediaService
521
- const mediaMetadata = await this.mediaService.uploadMedia(
522
- file,
523
- appointmentId, // Using appointmentId as ownerId
524
- accessLevel,
525
- collectionName,
526
- file instanceof File ? file.name : `file_${fileId}`
527
- );
528
-
529
- // Create and return a file value object
530
- const fileValue: FilledDocumentFileValue = {
531
- mediaId: mediaMetadata.id,
532
- url: mediaMetadata.url,
533
- name: mediaMetadata.name,
534
- contentType: mediaMetadata.contentType,
535
- size: mediaMetadata.size,
536
- uploadedAt: Date.now(),
537
- };
538
-
539
- return fileValue;
540
- }
541
-
542
- /**
543
- * Upload a signature image for a filled document.
544
- * This is a specialized version of uploadFileForFilledDocument specifically for signatures.
545
- *
546
- * @param appointmentId - ID of the appointment
547
- * @param formId - ID of the filled document
548
- * @param isUserForm - Boolean indicating if it's a user form or doctor form
549
- * @param signatureBlob - The signature image as a Blob
550
- * @returns The file value object to be stored in the document
551
- */
552
- async uploadSignatureForFilledDocument(
553
- appointmentId: string,
554
- formId: string,
555
- isUserForm: boolean,
556
- signatureBlob: Blob
557
- ): Promise<FilledDocumentFileValue> {
558
- console.log(
559
- `[FilledDocumentService] Uploading signature for form ${formId}`
560
- );
561
-
562
- // Generate a filename for the signature
563
- const signatureId = this.generateId();
564
- const signatureFile = new File(
565
- [signatureBlob],
566
- `signature_${signatureId}.png`,
567
- { type: "image/png" }
568
- );
569
-
570
- // Use the general file upload method
571
- return this.uploadFileForFilledDocument(
572
- appointmentId,
573
- formId,
574
- isUserForm,
575
- signatureFile
576
- );
577
- }
578
-
579
- /**
580
- * Delete a file using its mediaId.
581
- *
582
- * @param mediaId - ID of the media to delete
583
- * @returns Promise resolving when the deletion is complete
584
- */
585
- async deleteFile(mediaId: string): Promise<void> {
586
- console.log(
587
- `[FilledDocumentService] Deleting file with mediaId ${mediaId}`
588
- );
589
-
590
- if (!mediaId) {
591
- throw new Error("MediaId is required to delete a file");
592
- }
593
-
594
- // Delete the file using MediaService
595
- await this.mediaService.deleteMedia(mediaId);
596
- }
597
- }
1
+ import {
2
+ collection,
3
+ doc,
4
+ getDoc,
5
+ getDocs,
6
+ setDoc,
7
+ updateDoc,
8
+ query,
9
+ where,
10
+ orderBy,
11
+ limit,
12
+ startAfter,
13
+ QueryDocumentSnapshot,
14
+ } from "firebase/firestore";
15
+ import { BaseService } from "../base.service";
16
+ import {
17
+ // DocumentTemplate, // Not directly used in this file if relying on templateService
18
+ // DOCUMENTATION_TEMPLATES_COLLECTION, // No longer needed directly here if using templateService
19
+ // FILLED_DOCUMENTS_COLLECTION, // This will be replaced by subcollection paths
20
+ FilledDocument,
21
+ FilledDocumentStatus,
22
+ FilledDocumentFileValue,
23
+ USER_FORMS_SUBCOLLECTION,
24
+ DOCTOR_FORMS_SUBCOLLECTION,
25
+ } from "../../types"; // General types
26
+ import { APPOINTMENTS_COLLECTION } from "../../types/appointment"; // Specific import for the constant
27
+ import { DocumentationTemplateService } from "./documentation-template.service";
28
+ import {
29
+ MediaService,
30
+ MediaAccessLevel,
31
+ MediaMetadata,
32
+ } from "../media/media.service";
33
+ // Import the new validation schemas if you plan to use them for input validation here
34
+ // import { createFilledDocumentDataSchema, updateFilledDocumentDataSchema } from '../../validations/documentation-templates.schema';
35
+
36
+ /**
37
+ * Service for managing filled documents within appointment subcollections
38
+ */
39
+ export class FilledDocumentService extends BaseService {
40
+ // No single collectionRef anymore, paths will be dynamic
41
+ private readonly templateService: DocumentationTemplateService;
42
+ private readonly mediaService: MediaService;
43
+
44
+ constructor(...args: ConstructorParameters<typeof BaseService>) {
45
+ super(...args);
46
+ this.templateService = new DocumentationTemplateService(...args); // Pass db and other args
47
+ this.mediaService = new MediaService(...args); // Initialize media service with the same args
48
+ }
49
+
50
+ private getFormSubcollectionPath(
51
+ isUserForm: boolean
52
+ ): typeof USER_FORMS_SUBCOLLECTION | typeof DOCTOR_FORMS_SUBCOLLECTION {
53
+ return isUserForm ? USER_FORMS_SUBCOLLECTION : DOCTOR_FORMS_SUBCOLLECTION;
54
+ }
55
+
56
+ /**
57
+ * Create a new filled document within an appointment's subcollection.
58
+ * @param templateId - ID of the template to use.
59
+ * @param templateVersion - Version of the template to use.
60
+ * @param appointmentId - ID of the appointment this form belongs to.
61
+ * @param procedureId - ID of the procedure associated with this form.
62
+ * @param patientId - ID of the patient.
63
+ * @param practitionerId - ID of the practitioner (can be system/generic if patient is filling).
64
+ * @param clinicId - ID of the clinic.
65
+ * @param initialValues - Optional initial values for the form elements.
66
+ * @param initialStatus - Optional initial status for the form.
67
+ * @returns The created filled document.
68
+ */
69
+ async createFilledDocumentForAppointment(
70
+ templateId: string,
71
+ templateVersion: number,
72
+ appointmentId: string,
73
+ procedureId: string,
74
+ patientId: string,
75
+ practitionerId: string, // Consider if this is always available or if a placeholder is needed
76
+ clinicId: string, // Same consideration as practitionerId
77
+ initialValues: { [elementId: string]: any } = {},
78
+ initialStatus: FilledDocumentStatus = FilledDocumentStatus.DRAFT
79
+ ): Promise<FilledDocument> {
80
+ const template = await this.templateService.getTemplateById(
81
+ templateId,
82
+ templateVersion
83
+ );
84
+ if (!template) {
85
+ throw new Error(`Template with ID ${templateId} not found`);
86
+ }
87
+
88
+ const documentId = this.generateId();
89
+ const now = Date.now();
90
+ const isUserForm = template.isUserForm || false;
91
+ const formSubcollection = this.getFormSubcollectionPath(isUserForm);
92
+
93
+ const filledDocument: FilledDocument = {
94
+ id: documentId,
95
+ templateId,
96
+ templateVersion: template.version,
97
+ isUserForm: isUserForm, // Set based on template
98
+ isRequired: template.isRequired || false, // Inherit isRequired from the template
99
+ appointmentId: appointmentId, // NEW
100
+ procedureId: procedureId, // NEW
101
+ patientId,
102
+ practitionerId,
103
+ clinicId,
104
+ createdAt: now,
105
+ updatedAt: now,
106
+ values: initialValues,
107
+ status: initialStatus,
108
+ };
109
+
110
+ const docRef = doc(
111
+ this.db,
112
+ APPOINTMENTS_COLLECTION, // Replaced "appointments"
113
+ appointmentId,
114
+ formSubcollection,
115
+ documentId
116
+ );
117
+ await setDoc(docRef, filledDocument);
118
+
119
+ return filledDocument;
120
+ }
121
+
122
+ /**
123
+ * Get a specific filled document from an appointment's subcollection.
124
+ * @param appointmentId - ID of the appointment.
125
+ * @param formId - ID of the filled document.
126
+ * @param isUserForm - Boolean indicating if it's a user form or doctor form.
127
+ * @returns The filled document or null if not found.
128
+ */
129
+ async getFilledDocumentFromAppointmentById(
130
+ appointmentId: string,
131
+ formId: string,
132
+ isUserForm: boolean
133
+ ): Promise<FilledDocument | null> {
134
+ const formSubcollection = this.getFormSubcollectionPath(isUserForm);
135
+ const docRef = doc(
136
+ this.db,
137
+ APPOINTMENTS_COLLECTION, // Replaced "appointments"
138
+ appointmentId,
139
+ formSubcollection,
140
+ formId
141
+ );
142
+ const docSnap = await getDoc(docRef);
143
+
144
+ if (!docSnap.exists()) {
145
+ return null;
146
+ }
147
+ const data = docSnap.data() as FilledDocument;
148
+ // Ensure id is populated from Firestore document ID if not in data
149
+ if (!data.id) {
150
+ data.id = docSnap.id;
151
+ }
152
+ return data;
153
+ }
154
+
155
+ /**
156
+ * Update values or status in a filled document within an appointment's subcollection.
157
+ * @param appointmentId - ID of the appointment.
158
+ * @param formId - ID of the filled document to update.
159
+ * @param isUserForm - Boolean indicating if it's a user form or doctor form.
160
+ * @param values - Updated values for elements.
161
+ * @param status - Optional new status for the document.
162
+ * @returns The updated filled document.
163
+ */
164
+ async updateFilledDocumentInAppointment(
165
+ appointmentId: string,
166
+ formId: string,
167
+ isUserForm: boolean,
168
+ values?: { [elementId: string]: any },
169
+ status?: FilledDocumentStatus
170
+ ): Promise<FilledDocument> {
171
+ const formSubcollection = this.getFormSubcollectionPath(isUserForm);
172
+ const docRef = doc(
173
+ this.db,
174
+ APPOINTMENTS_COLLECTION, // Replaced "appointments"
175
+ appointmentId,
176
+ formSubcollection,
177
+ formId
178
+ );
179
+
180
+ const existingDoc = await this.getFilledDocumentFromAppointmentById(
181
+ appointmentId,
182
+ formId,
183
+ isUserForm
184
+ );
185
+
186
+ if (!existingDoc) {
187
+ throw new Error(
188
+ `Filled document with ID ${formId} not found in appointment ${appointmentId} ${formSubcollection}`
189
+ );
190
+ }
191
+
192
+ const updatePayload: Partial<FilledDocument> = {
193
+ updatedAt: Date.now(),
194
+ };
195
+
196
+ if (values) {
197
+ updatePayload.values = {
198
+ ...existingDoc.values,
199
+ ...values,
200
+ };
201
+ }
202
+ if (status) {
203
+ updatePayload.status = status;
204
+ }
205
+
206
+ if (
207
+ Object.keys(updatePayload).length === 1 &&
208
+ "updatedAt" in updatePayload
209
+ ) {
210
+ // Only updatedAt, no actual data change, so we can skip the update
211
+ // or just update timestamp if that is the desired behavior.
212
+ // For now, we proceed to update the timestamp at least.
213
+ }
214
+
215
+ await updateDoc(docRef, updatePayload);
216
+
217
+ return { ...existingDoc, ...updatePayload } as FilledDocument;
218
+ }
219
+
220
+ /**
221
+ * Get all filled user forms for a specific appointment.
222
+ * @param appointmentId ID of the appointment.
223
+ * @param pageSize Number of documents to retrieve.
224
+ * @param lastDoc Last document from previous page for pagination.
225
+ */
226
+ async getFilledUserFormsForAppointment(
227
+ appointmentId: string,
228
+ pageSize = 20,
229
+ lastDoc?: QueryDocumentSnapshot<FilledDocument>
230
+ ): Promise<{
231
+ documents: FilledDocument[];
232
+ lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
233
+ }> {
234
+ const subcollectionRef = collection(
235
+ this.db,
236
+ APPOINTMENTS_COLLECTION, // Replaced "appointments"
237
+ appointmentId,
238
+ USER_FORMS_SUBCOLLECTION
239
+ );
240
+ let q = query(
241
+ subcollectionRef,
242
+ orderBy("updatedAt", "desc"),
243
+ limit(pageSize)
244
+ );
245
+
246
+ if (lastDoc) {
247
+ q = query(q, startAfter(lastDoc));
248
+ }
249
+ return this.executeQuery(q);
250
+ }
251
+
252
+ /**
253
+ * Get all filled doctor forms for a specific appointment.
254
+ * @param appointmentId ID of the appointment.
255
+ * @param pageSize Number of documents to retrieve.
256
+ * @param lastDoc Last document from previous page for pagination.
257
+ */
258
+ async getFilledDoctorFormsForAppointment(
259
+ appointmentId: string,
260
+ pageSize = 20,
261
+ lastDoc?: QueryDocumentSnapshot<FilledDocument>
262
+ ): Promise<{
263
+ documents: FilledDocument[];
264
+ lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
265
+ }> {
266
+ const subcollectionRef = collection(
267
+ this.db,
268
+ APPOINTMENTS_COLLECTION, // Replaced "appointments"
269
+ appointmentId,
270
+ DOCTOR_FORMS_SUBCOLLECTION
271
+ );
272
+ let q = query(
273
+ subcollectionRef,
274
+ orderBy("updatedAt", "desc"),
275
+ limit(pageSize)
276
+ );
277
+
278
+ if (lastDoc) {
279
+ q = query(q, startAfter(lastDoc));
280
+ }
281
+ return this.executeQuery(q);
282
+ }
283
+
284
+ // Helper to execute query and return documents + lastDoc
285
+ private async executeQuery(
286
+ q: any // Firestore Query type
287
+ ): Promise<{
288
+ documents: FilledDocument[];
289
+ lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
290
+ }> {
291
+ const querySnapshot = await getDocs(q);
292
+ const documents: FilledDocument[] = [];
293
+ let lastVisible: QueryDocumentSnapshot<FilledDocument> | null = null;
294
+
295
+ querySnapshot.forEach((docSnapshot) => {
296
+ const data = docSnapshot.data() as FilledDocument;
297
+ // Ensure id is populated from Firestore document ID if not in data
298
+ if (!data.id) {
299
+ data.id = docSnapshot.id;
300
+ }
301
+ documents.push(data);
302
+ lastVisible = docSnapshot as QueryDocumentSnapshot<FilledDocument>;
303
+ });
304
+
305
+ return {
306
+ documents,
307
+ lastDoc: lastVisible,
308
+ };
309
+ }
310
+
311
+ // IMPORTANT: The following methods that query across all patients/practitioners/clinics
312
+ // (e.g., getFilledDocumentsByPatient, getFilledDocumentsByPractitioner)
313
+ // will NOT work correctly if FILLED_DOCUMENTS_COLLECTION is no longer a top-level collection
314
+ // and data is only in appointment subcollections. You would need to use
315
+ // Firestore Collection Group Queries for that, which require an index and a different query approach.
316
+ // These methods are left here for now but would need significant rework or removal.
317
+
318
+ /**
319
+ * Get filled documents for a patient (NEEDS REWORK for subcollections or Collection Group Query)
320
+ * @param patientId - ID of the patient
321
+ * @param pageSize - Number of documents to retrieve
322
+ * @param lastDoc - Last document from previous page for pagination
323
+ * @returns Array of filled documents and the last document for pagination
324
+ */
325
+ async getFilledDocumentsByPatient(
326
+ patientId: string,
327
+ pageSize = 20,
328
+ lastDoc?: QueryDocumentSnapshot<FilledDocument>
329
+ ): Promise<{
330
+ documents: FilledDocument[];
331
+ lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
332
+ }> {
333
+ console.warn(
334
+ "getFilledDocumentsByPatient needs rework for subcollection model or Collection Group Queries."
335
+ );
336
+ // Original logic commented out as this.collectionRef is no longer valid for this model
337
+ /*
338
+ let q = query(
339
+ this.collectionRef, // LINTER ERROR: this.collectionRef no longer exists
340
+ where("patientId", "==", patientId),
341
+ orderBy("updatedAt", "desc"),
342
+ limit(pageSize)
343
+ );
344
+
345
+ if (lastDoc) {
346
+ q = query(q, startAfter(lastDoc));
347
+ }
348
+
349
+ const querySnapshot = await getDocs(q);
350
+ const documents: FilledDocument[] = [];
351
+ let lastVisible: QueryDocumentSnapshot<FilledDocument> | null = null;
352
+
353
+ querySnapshot.forEach((doc) => {
354
+ documents.push(doc.data() as FilledDocument);
355
+ lastVisible = doc as QueryDocumentSnapshot<FilledDocument>;
356
+ });
357
+
358
+ return {
359
+ documents,
360
+ lastDoc: lastVisible,
361
+ };
362
+ */
363
+ return { documents: [], lastDoc: null }; // Placeholder return
364
+ }
365
+
366
+ /**
367
+ * Get filled documents for a practitioner (NEEDS REWORK for subcollections or Collection Group Query)
368
+ * @param practitionerId - ID of the practitioner
369
+ * @param pageSize - Number of documents to retrieve
370
+ * @param lastDoc - Last document from previous page for pagination
371
+ * @returns Array of filled documents and the last document for pagination
372
+ */
373
+ async getFilledDocumentsByPractitioner(
374
+ practitionerId: string,
375
+ pageSize = 20,
376
+ lastDoc?: QueryDocumentSnapshot<FilledDocument>
377
+ ): Promise<{
378
+ documents: FilledDocument[];
379
+ lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
380
+ }> {
381
+ console.warn(
382
+ "getFilledDocumentsByPractitioner needs rework for subcollection model or Collection Group Queries."
383
+ );
384
+ // Original logic commented out
385
+ /*
386
+ let q = query(
387
+ this.collectionRef, // LINTER ERROR
388
+ where("practitionerId", "==", practitionerId),
389
+ orderBy("updatedAt", "desc"),
390
+ limit(pageSize)
391
+ );
392
+ // ... rest of original logic ...
393
+ */
394
+ return { documents: [], lastDoc: null }; // Placeholder return
395
+ }
396
+
397
+ /**
398
+ * Get filled documents for a clinic (NEEDS REWORK for subcollections or Collection Group Query)
399
+ * @param clinicId - ID of the clinic
400
+ * @param pageSize - Number of documents to retrieve
401
+ * @param lastDoc - Last document from previous page for pagination
402
+ * @returns Array of filled documents and the last document for pagination
403
+ */
404
+ async getFilledDocumentsByClinic(
405
+ clinicId: string,
406
+ pageSize = 20,
407
+ lastDoc?: QueryDocumentSnapshot<FilledDocument>
408
+ ): Promise<{
409
+ documents: FilledDocument[];
410
+ lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
411
+ }> {
412
+ console.warn(
413
+ "getFilledDocumentsByClinic needs rework for subcollection model or Collection Group Queries."
414
+ );
415
+ // Original logic commented out
416
+ /*
417
+ let q = query(
418
+ this.collectionRef, // LINTER ERROR
419
+ where("clinicId", "==", clinicId),
420
+ orderBy("updatedAt", "desc"),
421
+ limit(pageSize)
422
+ );
423
+ // ... rest of original logic ...
424
+ */
425
+ return { documents: [], lastDoc: null }; // Placeholder return
426
+ }
427
+
428
+ /**
429
+ * Get filled documents by template (NEEDS REWORK for subcollections or Collection Group Query)
430
+ * @param templateId - ID of the template
431
+ * @param pageSize - Number of documents to retrieve
432
+ * @param lastDoc - Last document from previous page for pagination
433
+ * @returns Array of filled documents and the last document for pagination
434
+ */
435
+ async getFilledDocumentsByTemplate(
436
+ templateId: string,
437
+ pageSize = 20,
438
+ lastDoc?: QueryDocumentSnapshot<FilledDocument>
439
+ ): Promise<{
440
+ documents: FilledDocument[];
441
+ lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
442
+ }> {
443
+ console.warn(
444
+ "getFilledDocumentsByTemplate needs rework for subcollection model or Collection Group Queries."
445
+ );
446
+ // Original logic commented out
447
+ /*
448
+ let q = query(
449
+ this.collectionRef, // LINTER ERROR
450
+ where("templateId", "==", templateId),
451
+ orderBy("updatedAt", "desc"),
452
+ limit(pageSize)
453
+ );
454
+ // ... rest of original logic ...
455
+ */
456
+ return { documents: [], lastDoc: null }; // Placeholder return
457
+ }
458
+
459
+ /**
460
+ * Get filled documents by status (NEEDS REWORK for subcollections or Collection Group Query)
461
+ * @param status - Status to filter by
462
+ * @param pageSize - Number of documents to retrieve
463
+ * @param lastDoc - Last document from previous page for pagination
464
+ * @returns Array of filled documents and the last document for pagination
465
+ */
466
+ async getFilledDocumentsByStatus(
467
+ status: FilledDocumentStatus,
468
+ pageSize = 20,
469
+ lastDoc?: QueryDocumentSnapshot<FilledDocument>
470
+ ): Promise<{
471
+ documents: FilledDocument[];
472
+ lastDoc: QueryDocumentSnapshot<FilledDocument> | null;
473
+ }> {
474
+ console.warn(
475
+ "getFilledDocumentsByStatus needs rework for subcollection model or Collection Group Queries."
476
+ );
477
+ // Original logic commented out
478
+ /*
479
+ let q = query(
480
+ this.collectionRef, // LINTER ERROR
481
+ where("status", "==", status),
482
+ orderBy("updatedAt", "desc"),
483
+ limit(pageSize)
484
+ );
485
+ // ... rest of original logic ...
486
+ */
487
+ return { documents: [], lastDoc: null }; // Placeholder return
488
+ }
489
+
490
+ /**
491
+ * Upload a file for a filled document field without updating the document.
492
+ * This method only handles the upload and returns the file value to be used by the UI.
493
+ *
494
+ * @param appointmentId - ID of the appointment
495
+ * @param formId - ID of the filled document
496
+ * @param isUserForm - Boolean indicating if it's a user form or doctor form
497
+ * @param file - The file to upload
498
+ * @returns The file value object to be stored in the document
499
+ */
500
+ async uploadFileForFilledDocument(
501
+ appointmentId: string,
502
+ formId: string,
503
+ isUserForm: boolean,
504
+ file: File | Blob
505
+ ): Promise<FilledDocumentFileValue> {
506
+ console.log(
507
+ `[FilledDocumentService] Uploading file for form ${formId} in appointment ${appointmentId}`
508
+ );
509
+
510
+ // Generate a unique file ID
511
+ const fileId = this.generateId();
512
+
513
+ // Set the path according to the specified structure
514
+ const formType = isUserForm ? "user-form" : "doctor-form";
515
+ const collectionName = `${formType}/${formId}`;
516
+
517
+ // Always use CONFIDENTIAL access level
518
+ const accessLevel = MediaAccessLevel.CONFIDENTIAL;
519
+
520
+ // Upload the file using MediaService
521
+ const mediaMetadata = await this.mediaService.uploadMedia(
522
+ file,
523
+ appointmentId, // Using appointmentId as ownerId
524
+ accessLevel,
525
+ collectionName,
526
+ file instanceof File ? file.name : `file_${fileId}`
527
+ );
528
+
529
+ // Create and return a file value object
530
+ const fileValue: FilledDocumentFileValue = {
531
+ mediaId: mediaMetadata.id,
532
+ url: mediaMetadata.url,
533
+ name: mediaMetadata.name,
534
+ contentType: mediaMetadata.contentType,
535
+ size: mediaMetadata.size,
536
+ uploadedAt: Date.now(),
537
+ };
538
+
539
+ return fileValue;
540
+ }
541
+
542
+ /**
543
+ * Upload a signature image for a filled document.
544
+ * This is a specialized version of uploadFileForFilledDocument specifically for signatures.
545
+ *
546
+ * @param appointmentId - ID of the appointment
547
+ * @param formId - ID of the filled document
548
+ * @param isUserForm - Boolean indicating if it's a user form or doctor form
549
+ * @param signatureBlob - The signature image as a Blob
550
+ * @returns The file value object to be stored in the document
551
+ */
552
+ async uploadSignatureForFilledDocument(
553
+ appointmentId: string,
554
+ formId: string,
555
+ isUserForm: boolean,
556
+ signatureBlob: Blob
557
+ ): Promise<FilledDocumentFileValue> {
558
+ console.log(
559
+ `[FilledDocumentService] Uploading signature for form ${formId}`
560
+ );
561
+
562
+ // Generate a filename for the signature
563
+ const signatureId = this.generateId();
564
+ const signatureFile = new File(
565
+ [signatureBlob],
566
+ `signature_${signatureId}.png`,
567
+ { type: "image/png" }
568
+ );
569
+
570
+ // Use the general file upload method
571
+ return this.uploadFileForFilledDocument(
572
+ appointmentId,
573
+ formId,
574
+ isUserForm,
575
+ signatureFile
576
+ );
577
+ }
578
+
579
+ /**
580
+ * Delete a file using its mediaId.
581
+ *
582
+ * @param mediaId - ID of the media to delete
583
+ * @returns Promise resolving when the deletion is complete
584
+ */
585
+ async deleteFile(mediaId: string): Promise<void> {
586
+ console.log(
587
+ `[FilledDocumentService] Deleting file with mediaId ${mediaId}`
588
+ );
589
+
590
+ if (!mediaId) {
591
+ throw new Error("MediaId is required to delete a file");
592
+ }
593
+
594
+ // Delete the file using MediaService
595
+ await this.mediaService.deleteMedia(mediaId);
596
+ }
597
+ }