@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.
- package/dist/admin/index.d.mts +377 -222
- package/dist/admin/index.d.ts +377 -222
- package/dist/admin/index.js +625 -206
- package/dist/admin/index.mjs +624 -206
- package/dist/backoffice/index.d.mts +24 -0
- package/dist/backoffice/index.d.ts +24 -0
- package/dist/index.d.mts +292 -4
- package/dist/index.d.ts +292 -4
- package/dist/index.js +1142 -630
- package/dist/index.mjs +1137 -617
- package/package.json +2 -1
- package/src/__mocks__/firstore.ts +10 -10
- package/src/admin/aggregation/README.md +79 -79
- package/src/admin/aggregation/appointment/README.md +151 -129
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +2137 -2091
- package/src/admin/aggregation/appointment/index.ts +1 -1
- package/src/admin/aggregation/clinic/README.md +52 -52
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +966 -966
- package/src/admin/aggregation/clinic/index.ts +1 -1
- package/src/admin/aggregation/forms/README.md +13 -13
- package/src/admin/aggregation/forms/filled-forms.aggregation.service.ts +322 -322
- package/src/admin/aggregation/forms/index.ts +1 -1
- package/src/admin/aggregation/index.ts +8 -8
- package/src/admin/aggregation/patient/README.md +27 -27
- package/src/admin/aggregation/patient/index.ts +1 -1
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -141
- package/src/admin/aggregation/practitioner/README.md +42 -42
- package/src/admin/aggregation/practitioner/index.ts +1 -1
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -433
- package/src/admin/aggregation/practitioner-invite/index.ts +1 -1
- package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +961 -961
- package/src/admin/aggregation/procedure/README.md +43 -43
- package/src/admin/aggregation/procedure/index.ts +1 -1
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +702 -702
- package/src/admin/aggregation/reviews/index.ts +1 -1
- package/src/admin/aggregation/reviews/reviews.aggregation.service.ts +689 -689
- package/src/admin/analytics/analytics.admin.service.ts +278 -278
- package/src/admin/analytics/index.ts +2 -2
- package/src/admin/booking/README.md +184 -125
- package/src/admin/booking/booking.admin.ts +1330 -1073
- package/src/admin/booking/booking.calculator.ts +850 -712
- package/src/admin/booking/booking.types.ts +76 -59
- package/src/admin/booking/index.ts +3 -3
- package/src/admin/booking/timezones-problem.md +185 -185
- package/src/admin/calendar/README.md +62 -7
- package/src/admin/calendar/calendar.admin.service.ts +345 -345
- package/src/admin/calendar/index.ts +2 -1
- package/src/admin/calendar/resource-calendar.admin.ts +198 -0
- package/src/admin/documentation-templates/document-manager.admin.ts +260 -260
- package/src/admin/documentation-templates/index.ts +1 -1
- package/src/admin/free-consultation/free-consultation-utils.admin.ts +148 -148
- package/src/admin/free-consultation/index.ts +1 -1
- package/src/admin/index.ts +83 -83
- package/src/admin/logger/index.ts +78 -78
- package/src/admin/mailing/README.md +139 -139
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +1253 -1253
- package/src/admin/mailing/appointment/index.ts +1 -1
- package/src/admin/mailing/appointment/templates/patient/appointment-confirmed.html +40 -40
- package/src/admin/mailing/base.mailing.service.ts +208 -208
- package/src/admin/mailing/clinicWelcome/clinicWelcome.mailing.ts +292 -292
- package/src/admin/mailing/clinicWelcome/index.ts +1 -1
- package/src/admin/mailing/clinicWelcome/templates/welcome.template.ts +225 -225
- package/src/admin/mailing/index.ts +5 -5
- package/src/admin/mailing/patientInvite/index.ts +2 -2
- package/src/admin/mailing/patientInvite/patientInvite.mailing.ts +415 -415
- package/src/admin/mailing/patientInvite/templates/invitation.template.ts +105 -105
- package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -611
- package/src/admin/mailing/practitionerInvite/index.ts +2 -2
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +395 -395
- package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -155
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -101
- package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -228
- package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -242
- package/src/admin/notifications/index.ts +1 -1
- package/src/admin/notifications/notifications.admin.ts +818 -818
- package/src/admin/requirements/README.md +128 -128
- package/src/admin/requirements/index.ts +1 -1
- package/src/admin/requirements/patient-requirements.admin.service.ts +475 -475
- package/src/admin/users/index.ts +1 -1
- package/src/admin/users/user-profile.admin.ts +405 -405
- package/src/backoffice/constants/certification.constants.ts +13 -13
- package/src/backoffice/constants/index.ts +1 -1
- package/src/backoffice/errors/backoffice.errors.ts +181 -181
- package/src/backoffice/errors/index.ts +1 -1
- package/src/backoffice/expo-safe/README.md +26 -26
- package/src/backoffice/expo-safe/index.ts +41 -41
- package/src/backoffice/index.ts +5 -5
- package/src/backoffice/services/FIXES_README.md +102 -102
- package/src/backoffice/services/README.md +57 -57
- package/src/backoffice/services/analytics.service.proposal.md +863 -863
- package/src/backoffice/services/analytics.service.summary.md +143 -143
- package/src/backoffice/services/brand.service.ts +260 -260
- package/src/backoffice/services/category.service.ts +384 -384
- package/src/backoffice/services/constants.service.ts +385 -385
- package/src/backoffice/services/documentation-template.service.ts +202 -202
- package/src/backoffice/services/index.ts +10 -10
- package/src/backoffice/services/migrate-products.ts +116 -116
- package/src/backoffice/services/product.service.ts +557 -557
- package/src/backoffice/services/requirement.service.ts +235 -235
- package/src/backoffice/services/subcategory.service.ts +461 -461
- package/src/backoffice/services/technology.service.ts +1153 -1153
- package/src/backoffice/types/README.md +12 -12
- package/src/backoffice/types/admin-constants.types.ts +69 -69
- package/src/backoffice/types/brand.types.ts +29 -29
- package/src/backoffice/types/category.types.ts +67 -67
- package/src/backoffice/types/documentation-templates.types.ts +28 -28
- package/src/backoffice/types/index.ts +10 -10
- package/src/backoffice/types/procedure-product.types.ts +38 -38
- package/src/backoffice/types/product.types.ts +239 -239
- package/src/backoffice/types/requirement.types.ts +63 -63
- package/src/backoffice/types/static/README.md +18 -18
- package/src/backoffice/types/static/blocking-condition.types.ts +21 -21
- package/src/backoffice/types/static/certification.types.ts +37 -37
- package/src/backoffice/types/static/contraindication.types.ts +19 -19
- package/src/backoffice/types/static/index.ts +6 -6
- package/src/backoffice/types/static/pricing.types.ts +16 -16
- package/src/backoffice/types/static/procedure-family.types.ts +14 -14
- package/src/backoffice/types/static/treatment-benefit.types.ts +22 -22
- package/src/backoffice/types/subcategory.types.ts +34 -34
- package/src/backoffice/types/technology.types.ts +168 -168
- package/src/backoffice/validations/index.ts +1 -1
- package/src/backoffice/validations/schemas.ts +164 -164
- package/src/config/__mocks__/firebase.ts +99 -99
- package/src/config/firebase.ts +78 -78
- package/src/config/index.ts +17 -17
- package/src/config/tiers.config.ts +255 -229
- package/src/errors/auth.error.ts +6 -6
- package/src/errors/auth.errors.ts +211 -211
- package/src/errors/clinic.errors.ts +32 -32
- package/src/errors/firebase.errors.ts +47 -47
- package/src/errors/user.errors.ts +99 -99
- package/src/index.backup.ts +407 -407
- package/src/index.ts +6 -6
- package/src/locales/en.ts +31 -31
- package/src/recommender/admin/index.ts +1 -1
- package/src/recommender/admin/services/recommender.service.admin.ts +5 -5
- package/src/recommender/front/index.ts +1 -1
- package/src/recommender/front/services/onboarding.service.ts +5 -5
- package/src/recommender/front/services/recommender.service.ts +3 -3
- package/src/recommender/index.ts +1 -1
- package/src/services/PATIENTAUTH.MD +197 -197
- package/src/services/README.md +106 -106
- package/src/services/__tests__/auth/auth.mock.test.ts +17 -17
- package/src/services/__tests__/auth/auth.setup.ts +298 -298
- package/src/services/__tests__/auth.service.test.ts +310 -310
- package/src/services/__tests__/base.service.test.ts +36 -36
- package/src/services/__tests__/user.service.test.ts +530 -530
- package/src/services/analytics/ARCHITECTURE.md +199 -199
- package/src/services/analytics/CLOUD_FUNCTIONS.md +225 -225
- package/src/services/analytics/GROUPED_ANALYTICS.md +501 -501
- package/src/services/analytics/QUICK_START.md +393 -393
- package/src/services/analytics/README.md +304 -304
- package/src/services/analytics/SUMMARY.md +141 -141
- package/src/services/analytics/TRENDS.md +380 -380
- package/src/services/analytics/USAGE_GUIDE.md +518 -518
- package/src/services/analytics/analytics-cloud.service.ts +222 -222
- package/src/services/analytics/analytics.service.ts +2148 -2148
- package/src/services/analytics/index.ts +4 -4
- package/src/services/analytics/review-analytics.service.ts +941 -941
- package/src/services/analytics/utils/appointment-filtering.utils.ts +138 -138
- package/src/services/analytics/utils/cost-calculation.utils.ts +182 -182
- package/src/services/analytics/utils/grouping.utils.ts +434 -434
- package/src/services/analytics/utils/stored-analytics.utils.ts +347 -347
- package/src/services/analytics/utils/time-calculation.utils.ts +186 -186
- package/src/services/analytics/utils/trend-calculation.utils.ts +200 -200
- package/src/services/appointment/README.md +17 -17
- package/src/services/appointment/appointment.service.ts +2943 -2941
- package/src/services/appointment/index.ts +1 -1
- package/src/services/appointment/utils/appointment.utils.ts +620 -620
- package/src/services/appointment/utils/extended-procedure.utils.ts +354 -354
- package/src/services/appointment/utils/form-initialization.utils.ts +516 -516
- package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
- package/src/services/appointment/utils/zone-management.utils.ts +468 -468
- package/src/services/appointment/utils/zone-photo.utils.ts +302 -302
- package/src/services/auth/auth.service.ts +1435 -1435
- package/src/services/auth/auth.v2.service.ts +961 -961
- package/src/services/auth/index.ts +7 -7
- package/src/services/auth/utils/error.utils.ts +90 -90
- package/src/services/auth/utils/firebase.utils.ts +49 -49
- package/src/services/auth/utils/index.ts +21 -21
- package/src/services/auth/utils/practitioner.utils.ts +125 -125
- package/src/services/base.service.ts +41 -41
- package/src/services/calendar/calendar.service.ts +1077 -1077
- package/src/services/calendar/calendar.v2.service.ts +1693 -1693
- package/src/services/calendar/calendar.v3.service.ts +313 -313
- package/src/services/calendar/externalCalendar.service.ts +178 -178
- package/src/services/calendar/index.ts +5 -5
- package/src/services/calendar/synced-calendars.service.ts +743 -743
- package/src/services/calendar/utils/appointment.utils.ts +265 -265
- package/src/services/calendar/utils/calendar-event.utils.ts +676 -676
- package/src/services/calendar/utils/clinic.utils.ts +237 -237
- package/src/services/calendar/utils/docs.utils.ts +157 -157
- package/src/services/calendar/utils/google-calendar.utils.ts +697 -697
- package/src/services/calendar/utils/index.ts +8 -8
- package/src/services/calendar/utils/patient.utils.ts +198 -198
- package/src/services/calendar/utils/practitioner.utils.ts +221 -221
- package/src/services/calendar/utils/synced-calendar.utils.ts +472 -472
- package/src/services/clinic/README.md +204 -204
- package/src/services/clinic/__tests__/clinic-admin.service.test.ts +265 -265
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +222 -222
- package/src/services/clinic/__tests__/clinic.service.test.ts +302 -302
- package/src/services/clinic/billing-transactions.service.ts +217 -217
- package/src/services/clinic/clinic-admin.service.ts +202 -202
- package/src/services/clinic/clinic-group.service.ts +310 -310
- package/src/services/clinic/clinic.service.ts +720 -720
- package/src/services/clinic/index.ts +5 -5
- package/src/services/clinic/practitioner-invite.service.ts +519 -519
- package/src/services/clinic/utils/admin.utils.ts +551 -551
- package/src/services/clinic/utils/clinic-group.utils.ts +646 -646
- package/src/services/clinic/utils/clinic.utils.ts +1023 -1023
- package/src/services/clinic/utils/filter.utils.d.ts +23 -23
- package/src/services/clinic/utils/filter.utils.ts +462 -462
- package/src/services/clinic/utils/index.ts +10 -10
- package/src/services/clinic/utils/photos.utils.ts +188 -188
- package/src/services/clinic/utils/search.utils.ts +83 -83
- package/src/services/clinic/utils/tag.utils.ts +124 -124
- package/src/services/documentation-templates/documentation-template.service.ts +537 -537
- package/src/services/documentation-templates/filled-document.service.ts +597 -597
- package/src/services/documentation-templates/index.ts +2 -2
- package/src/services/index.ts +16 -15
- package/src/services/media/index.ts +1 -1
- package/src/services/media/media.service.ts +418 -418
- package/src/services/notifications/__tests__/notification.service.test.ts +242 -242
- package/src/services/notifications/index.ts +1 -1
- package/src/services/notifications/notification.service.ts +215 -215
- package/src/services/patient/README.md +48 -48
- package/src/services/patient/To-Do.md +43 -43
- package/src/services/patient/__tests__/patient.service.test.ts +286 -286
- package/src/services/patient/index.ts +2 -2
- package/src/services/patient/patient.service.ts +1021 -1021
- package/src/services/patient/patientRequirements.service.ts +309 -309
- package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
- package/src/services/patient/utils/body-assessment.utils.ts +159 -159
- package/src/services/patient/utils/clinic.utils.ts +159 -159
- package/src/services/patient/utils/docs.utils.ts +142 -142
- package/src/services/patient/utils/hair-scalp-assessment.utils.ts +158 -158
- package/src/services/patient/utils/index.ts +9 -9
- package/src/services/patient/utils/location.utils.ts +126 -126
- package/src/services/patient/utils/medical-stuff.utils.ts +143 -143
- package/src/services/patient/utils/medical.utils.ts +458 -458
- package/src/services/patient/utils/practitioner.utils.ts +260 -260
- package/src/services/patient/utils/pre-surgical-assessment.utils.ts +161 -161
- package/src/services/patient/utils/profile.utils.ts +510 -510
- package/src/services/patient/utils/sensitive.utils.ts +260 -260
- package/src/services/patient/utils/skin-quality-assessment.utils.ts +160 -160
- package/src/services/patient/utils/token.utils.ts +211 -211
- package/src/services/practitioner/README.md +145 -145
- package/src/services/practitioner/index.ts +1 -1
- package/src/services/practitioner/practitioner.service.ts +2355 -2354
- package/src/services/procedure/README.md +163 -163
- package/src/services/procedure/index.ts +1 -1
- package/src/services/procedure/procedure.service.ts +2521 -2521
- package/src/services/resource/README.md +119 -0
- package/src/services/resource/index.ts +1 -0
- package/src/services/resource/resource.service.ts +555 -0
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +745 -745
- package/src/services/tier-enforcement.ts +240 -240
- package/src/services/user/index.ts +1 -1
- package/src/services/user/user.service.ts +533 -533
- package/src/services/user/user.v2.service.ts +467 -467
- package/src/types/analytics/analytics.types.ts +597 -597
- package/src/types/analytics/grouped-analytics.types.ts +173 -173
- package/src/types/analytics/index.ts +4 -4
- package/src/types/analytics/stored-analytics.types.ts +137 -137
- package/src/types/appointment/index.ts +524 -517
- package/src/types/calendar/index.ts +261 -260
- package/src/types/calendar/synced-calendar.types.ts +66 -66
- package/src/types/clinic/index.ts +530 -529
- package/src/types/clinic/practitioner-invite.types.ts +91 -91
- package/src/types/clinic/preferences.types.ts +159 -159
- package/src/types/clinic/rbac.types.ts +64 -63
- package/src/types/clinic/to-do +3 -3
- package/src/types/documentation-templates/index.ts +308 -308
- package/src/types/index.ts +50 -47
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +300 -300
- package/src/types/patient/aesthetic-analysis.types.ts +66 -66
- package/src/types/patient/allergies.ts +58 -58
- package/src/types/patient/body-assessment.types.ts +93 -93
- package/src/types/patient/hair-scalp-assessment.types.ts +98 -98
- package/src/types/patient/index.ts +279 -279
- package/src/types/patient/medical-info.types.ts +152 -152
- package/src/types/patient/patient-requirements.ts +92 -92
- package/src/types/patient/pre-surgical-assessment.types.ts +95 -95
- package/src/types/patient/skin-quality-assessment.types.ts +105 -105
- package/src/types/patient/token.types.ts +61 -61
- package/src/types/practitioner/index.ts +208 -208
- package/src/types/procedure/index.ts +189 -183
- package/src/types/profile/index.ts +39 -39
- package/src/types/resource/README.md +153 -0
- package/src/types/resource/index.ts +199 -0
- package/src/types/reviews/index.ts +132 -132
- package/src/types/tz-lookup.d.ts +4 -4
- package/src/types/user/index.ts +60 -60
- package/src/utils/TIMESTAMPS.md +176 -176
- package/src/utils/TimestampUtils.ts +241 -241
- package/src/utils/index.ts +1 -1
- package/src/validations/README.md +94 -0
- package/src/validations/appointment.schema.ts +589 -589
- package/src/validations/calendar.schema.ts +225 -225
- package/src/validations/clinic.schema.ts +494 -494
- package/src/validations/common.schema.ts +25 -25
- package/src/validations/documentation-templates/index.ts +1 -1
- package/src/validations/documentation-templates/template.schema.ts +220 -220
- package/src/validations/documentation-templates.schema.ts +10 -10
- package/src/validations/index.ts +21 -20
- package/src/validations/media.schema.ts +10 -10
- package/src/validations/notification.schema.ts +90 -90
- package/src/validations/patient/aesthetic-analysis.schema.ts +55 -55
- package/src/validations/patient/body-assessment.schema.ts +82 -82
- package/src/validations/patient/hair-scalp-assessment.schema.ts +70 -70
- package/src/validations/patient/medical-info.schema.ts +177 -177
- package/src/validations/patient/patient-requirements.schema.ts +84 -84
- package/src/validations/patient/pre-surgical-assessment.schema.ts +78 -78
- package/src/validations/patient/skin-quality-assessment.schema.ts +70 -70
- package/src/validations/patient/token.schema.ts +29 -29
- package/src/validations/patient.schema.ts +217 -217
- package/src/validations/practitioner.schema.ts +224 -224
- package/src/validations/procedure-product.schema.ts +41 -41
- package/src/validations/procedure.schema.ts +136 -124
- package/src/validations/profile-info.schema.ts +41 -41
- package/src/validations/resource.schema.ts +57 -0
- package/src/validations/reviews.schema.ts +195 -195
- package/src/validations/schemas.ts +109 -109
- package/src/validations/shared.schema.ts +78 -78
|
@@ -1,516 +1,516 @@
|
|
|
1
|
-
import { Firestore, collection, doc, addDoc, deleteDoc, getDocs, query, where, serverTimestamp, getDoc, updateDoc } from 'firebase/firestore';
|
|
2
|
-
import {
|
|
3
|
-
DocumentTemplate,
|
|
4
|
-
FilledDocumentStatus,
|
|
5
|
-
DOCTOR_FORMS_SUBCOLLECTION,
|
|
6
|
-
USER_FORMS_SUBCOLLECTION,
|
|
7
|
-
DOCUMENTATION_TEMPLATES_COLLECTION,
|
|
8
|
-
FilledDocument,
|
|
9
|
-
} from '../../../types/documentation-templates';
|
|
10
|
-
import {
|
|
11
|
-
APPOINTMENTS_COLLECTION,
|
|
12
|
-
LinkedFormInfo,
|
|
13
|
-
} from '../../../types/appointment';
|
|
14
|
-
import { TechnologyDocumentationTemplate } from '../../../backoffice/types/technology.types';
|
|
15
|
-
|
|
16
|
-
export interface InitializeExtendedProcedureFormsResult {
|
|
17
|
-
initializedFormsInfo: LinkedFormInfo[];
|
|
18
|
-
pendingUserFormsIds: string[];
|
|
19
|
-
allLinkedFormIds: string[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Determines if a form template is procedure-specific or general/shared
|
|
24
|
-
* Uses explicit tags first, then falls back to heuristics
|
|
25
|
-
*
|
|
26
|
-
* Priority order:
|
|
27
|
-
* 1. "procedure-specific" tag → always procedure-specific
|
|
28
|
-
* 2. "shared" tag → always shared
|
|
29
|
-
* 3. Consent-related tags ("consent", "consent-form") → procedure-specific
|
|
30
|
-
* 4. Title contains "consent" → procedure-specific
|
|
31
|
-
* 5. Default → shared (general forms)
|
|
32
|
-
*
|
|
33
|
-
* @param template DocumentTemplate to check
|
|
34
|
-
* @returns true if procedure-specific, false if general/shared
|
|
35
|
-
*/
|
|
36
|
-
function isProcedureSpecificForm(template: DocumentTemplate): boolean {
|
|
37
|
-
const tags = template.tags || [];
|
|
38
|
-
const titleLower = template.title.toLowerCase();
|
|
39
|
-
|
|
40
|
-
// Priority 1: Explicit "procedure-specific" tag → always procedure-specific
|
|
41
|
-
if (tags.includes('procedure-specific')) {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Priority 2: Explicit "shared" tag → always shared
|
|
46
|
-
if (tags.includes('shared')) {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Priority 3: Consent-related tags → procedure-specific
|
|
51
|
-
if (tags.some(tag => {
|
|
52
|
-
const tagLower = tag.toLowerCase();
|
|
53
|
-
return tagLower.includes('consent') || tagLower === 'consent-form';
|
|
54
|
-
})) {
|
|
55
|
-
return true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Priority 4: Check title for "consent" → procedure-specific
|
|
59
|
-
if (titleLower.includes('consent')) {
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Priority 5: Default → shared (general forms)
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Checks if a form with the given templateId already exists in the appointment
|
|
69
|
-
* @param db Firestore instance
|
|
70
|
-
* @param appointmentId Appointment ID
|
|
71
|
-
* @param templateId Template ID to check
|
|
72
|
-
* @param isUserForm Whether to check user-forms or doctor-forms subcollection
|
|
73
|
-
* @returns Existing FilledDocument or null if not found
|
|
74
|
-
*/
|
|
75
|
-
async function findExistingFormByTemplate(
|
|
76
|
-
db: Firestore,
|
|
77
|
-
appointmentId: string,
|
|
78
|
-
templateId: string,
|
|
79
|
-
isUserForm: boolean
|
|
80
|
-
): Promise<FilledDocument | null> {
|
|
81
|
-
const formSubcollection = isUserForm
|
|
82
|
-
? USER_FORMS_SUBCOLLECTION
|
|
83
|
-
: DOCTOR_FORMS_SUBCOLLECTION;
|
|
84
|
-
|
|
85
|
-
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
86
|
-
const formsCollectionRef = collection(appointmentRef, formSubcollection);
|
|
87
|
-
|
|
88
|
-
const q = query(
|
|
89
|
-
formsCollectionRef,
|
|
90
|
-
where('templateId', '==', templateId)
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const querySnapshot = await getDocs(q);
|
|
94
|
-
|
|
95
|
-
if (querySnapshot.empty) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Return the first matching form (should only be one for general forms)
|
|
100
|
-
const docSnap = querySnapshot.docs[0];
|
|
101
|
-
const data = docSnap.data() as FilledDocument;
|
|
102
|
-
|
|
103
|
-
// Ensure id is populated from Firestore document ID if not in data
|
|
104
|
-
if (!data.id) {
|
|
105
|
-
data.id = docSnap.id;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return data;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Initializes forms for an extended procedure using client-side Firestore
|
|
113
|
-
* Similar to DocumentManagerAdminService but for client-side usage
|
|
114
|
-
* @param db Firestore instance
|
|
115
|
-
* @param appointmentId Appointment ID
|
|
116
|
-
* @param procedureId Procedure ID for forms
|
|
117
|
-
* @param technologyTemplates Technology documentation templates
|
|
118
|
-
* @param patientId Patient ID
|
|
119
|
-
* @param practitionerId Practitioner ID
|
|
120
|
-
* @param clinicId Clinic ID
|
|
121
|
-
* @returns Form initialization result
|
|
122
|
-
*/
|
|
123
|
-
export async function initializeFormsForExtendedProcedure(
|
|
124
|
-
db: Firestore,
|
|
125
|
-
appointmentId: string,
|
|
126
|
-
procedureId: string,
|
|
127
|
-
technologyTemplates: TechnologyDocumentationTemplate[],
|
|
128
|
-
patientId: string,
|
|
129
|
-
practitionerId: string,
|
|
130
|
-
clinicId: string
|
|
131
|
-
): Promise<InitializeExtendedProcedureFormsResult> {
|
|
132
|
-
const initializedFormsInfo: LinkedFormInfo[] = [];
|
|
133
|
-
const pendingUserFormsIds: string[] = [];
|
|
134
|
-
const allLinkedFormIds: string[] = [];
|
|
135
|
-
|
|
136
|
-
if (!technologyTemplates || technologyTemplates.length === 0) {
|
|
137
|
-
console.log(
|
|
138
|
-
`[FormInit] No document templates to initialize for extended procedure ${procedureId} in appointment ${appointmentId}.`
|
|
139
|
-
);
|
|
140
|
-
return {
|
|
141
|
-
initializedFormsInfo,
|
|
142
|
-
pendingUserFormsIds,
|
|
143
|
-
allLinkedFormIds,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Fetch all template documents
|
|
148
|
-
const templateIds = technologyTemplates.map((t) => t.templateId);
|
|
149
|
-
|
|
150
|
-
// Note: Client-side Firestore doesn't support 'in' queries with more than 10 items
|
|
151
|
-
// We'll fetch templates one by one for now (can be optimized later)
|
|
152
|
-
const templatesMap = new Map<string, DocumentTemplate>();
|
|
153
|
-
|
|
154
|
-
for (const templateId of templateIds) {
|
|
155
|
-
try {
|
|
156
|
-
const templateDoc = doc(db, DOCUMENTATION_TEMPLATES_COLLECTION, templateId);
|
|
157
|
-
const templateSnap = await getDoc(templateDoc);
|
|
158
|
-
|
|
159
|
-
if (templateSnap.exists()) {
|
|
160
|
-
templatesMap.set(templateId, templateSnap.data() as DocumentTemplate);
|
|
161
|
-
} else {
|
|
162
|
-
console.warn(
|
|
163
|
-
`[FormInit] Template ${templateId} not found in Firestore.`
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
} catch (error) {
|
|
167
|
-
console.error(`[FormInit] Error fetching template ${templateId}:`, error);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Initialize forms for each template (both user forms and doctor forms for extended procedures)
|
|
172
|
-
for (const templateRef of technologyTemplates) {
|
|
173
|
-
const template = templatesMap.get(templateRef.templateId);
|
|
174
|
-
if (!template) {
|
|
175
|
-
console.warn(
|
|
176
|
-
`[FormInit] Template ${templateRef.templateId} not found in Firestore.`
|
|
177
|
-
);
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const isRequired = templateRef.isRequired;
|
|
182
|
-
const isUserForm = templateRef.isUserForm || false;
|
|
183
|
-
const formSubcollectionPath = isUserForm
|
|
184
|
-
? USER_FORMS_SUBCOLLECTION
|
|
185
|
-
: DOCTOR_FORMS_SUBCOLLECTION;
|
|
186
|
-
|
|
187
|
-
// Check if form is procedure-specific or general/shared
|
|
188
|
-
const isProcedureSpecific = isProcedureSpecificForm(template);
|
|
189
|
-
|
|
190
|
-
// For general/shared forms, check if form already exists in appointment
|
|
191
|
-
let existingForm: FilledDocument | null = null;
|
|
192
|
-
if (!isProcedureSpecific) {
|
|
193
|
-
try {
|
|
194
|
-
existingForm = await findExistingFormByTemplate(
|
|
195
|
-
db,
|
|
196
|
-
appointmentId,
|
|
197
|
-
templateRef.templateId,
|
|
198
|
-
isUserForm
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
if (existingForm) {
|
|
202
|
-
console.log(
|
|
203
|
-
`[FormInit] Found existing shared form ${existingForm.id} (template: ${template.id}) for appointment ${appointmentId}. Reusing instead of creating duplicate.`
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
// Reuse existing form - add to linkedForms but don't create new document
|
|
207
|
-
// Note: We still add it to allLinkedFormIds to ensure it's tracked,
|
|
208
|
-
// but we don't add it again if it's already in the appointment's linkedForms
|
|
209
|
-
// (This will be handled by the caller merging the arrays)
|
|
210
|
-
|
|
211
|
-
const linkedForm: LinkedFormInfo = {
|
|
212
|
-
formId: existingForm.id,
|
|
213
|
-
templateId: template.id,
|
|
214
|
-
templateVersion: template.version,
|
|
215
|
-
title: template.title,
|
|
216
|
-
isUserForm: isUserForm,
|
|
217
|
-
isRequired: isRequired,
|
|
218
|
-
sortingOrder: templateRef.sortingOrder,
|
|
219
|
-
status: existingForm.status || FilledDocumentStatus.PENDING,
|
|
220
|
-
path: `${APPOINTMENTS_COLLECTION}/${appointmentId}/${formSubcollectionPath}/${existingForm.id}`,
|
|
221
|
-
procedureId: procedureId, // Track which procedure this form belongs to
|
|
222
|
-
};
|
|
223
|
-
initializedFormsInfo.push(linkedForm);
|
|
224
|
-
|
|
225
|
-
// Add to allLinkedFormIds if not already present (to avoid duplicates)
|
|
226
|
-
if (!allLinkedFormIds.includes(existingForm.id)) {
|
|
227
|
-
allLinkedFormIds.push(existingForm.id);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Add to pendingUserFormsIds if it's a required user form and not already completed
|
|
231
|
-
if (isUserForm && isRequired && existingForm.status === FilledDocumentStatus.PENDING) {
|
|
232
|
-
if (!pendingUserFormsIds.includes(existingForm.id)) {
|
|
233
|
-
pendingUserFormsIds.push(existingForm.id);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
continue; // Skip creating new form, reuse existing one
|
|
238
|
-
}
|
|
239
|
-
} catch (error) {
|
|
240
|
-
console.warn(
|
|
241
|
-
`[FormInit] Error checking for existing form (template: ${templateRef.templateId}):`,
|
|
242
|
-
error
|
|
243
|
-
);
|
|
244
|
-
// Continue to create new form if check fails
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Create new form document (either procedure-specific or general form not found)
|
|
249
|
-
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
250
|
-
const formsCollectionRef = collection(appointmentRef, formSubcollectionPath);
|
|
251
|
-
|
|
252
|
-
const filledDocumentData = {
|
|
253
|
-
templateId: templateRef.templateId,
|
|
254
|
-
templateVersion: template.version,
|
|
255
|
-
isUserForm: isUserForm,
|
|
256
|
-
isRequired: isRequired,
|
|
257
|
-
appointmentId: appointmentId,
|
|
258
|
-
procedureId: procedureId,
|
|
259
|
-
patientId: patientId,
|
|
260
|
-
practitionerId: practitionerId,
|
|
261
|
-
clinicId: clinicId,
|
|
262
|
-
createdAt: serverTimestamp(),
|
|
263
|
-
updatedAt: serverTimestamp(),
|
|
264
|
-
values: {},
|
|
265
|
-
status: FilledDocumentStatus.PENDING,
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
const docRef = await addDoc(formsCollectionRef, filledDocumentData);
|
|
270
|
-
const filledDocumentId = docRef.id;
|
|
271
|
-
|
|
272
|
-
// Update the document to include the id field in the data (for consistency)
|
|
273
|
-
await updateDoc(docRef, { id: filledDocumentId });
|
|
274
|
-
|
|
275
|
-
// Add to pendingUserFormsIds if it's a required user form
|
|
276
|
-
if (isUserForm && isRequired) {
|
|
277
|
-
pendingUserFormsIds.push(filledDocumentId);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
allLinkedFormIds.push(filledDocumentId);
|
|
281
|
-
|
|
282
|
-
const linkedForm: LinkedFormInfo = {
|
|
283
|
-
formId: filledDocumentId,
|
|
284
|
-
templateId: template.id,
|
|
285
|
-
templateVersion: template.version,
|
|
286
|
-
title: template.title,
|
|
287
|
-
isUserForm: isUserForm,
|
|
288
|
-
isRequired: isRequired,
|
|
289
|
-
sortingOrder: templateRef.sortingOrder,
|
|
290
|
-
status: FilledDocumentStatus.PENDING,
|
|
291
|
-
path: docRef.path,
|
|
292
|
-
procedureId: procedureId, // Track which procedure this form belongs to
|
|
293
|
-
};
|
|
294
|
-
initializedFormsInfo.push(linkedForm);
|
|
295
|
-
|
|
296
|
-
const formType = isProcedureSpecific ? 'procedure-specific' : 'general/shared';
|
|
297
|
-
console.log(
|
|
298
|
-
`[FormInit] Created ${formType} FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
|
|
299
|
-
);
|
|
300
|
-
} catch (error) {
|
|
301
|
-
console.error(
|
|
302
|
-
`[FormInit] Error creating form for template ${templateRef.templateId}:`,
|
|
303
|
-
error
|
|
304
|
-
);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return { initializedFormsInfo, pendingUserFormsIds, allLinkedFormIds };
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Removes all forms associated with a specific procedure from an appointment
|
|
313
|
-
* Only removes procedure-specific forms. Shared forms are kept if still referenced by other procedures.
|
|
314
|
-
* Removes both user forms and doctor forms
|
|
315
|
-
* @param db Firestore instance
|
|
316
|
-
* @param appointmentId Appointment ID
|
|
317
|
-
* @param procedureId Procedure ID to remove forms for
|
|
318
|
-
* @returns Array of removed form IDs
|
|
319
|
-
*/
|
|
320
|
-
export async function removeFormsForExtendedProcedure(
|
|
321
|
-
db: Firestore,
|
|
322
|
-
appointmentId: string,
|
|
323
|
-
procedureId: string
|
|
324
|
-
): Promise<string[]> {
|
|
325
|
-
const removedFormIds: string[] = [];
|
|
326
|
-
|
|
327
|
-
// Get appointment to check linkedForms
|
|
328
|
-
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
329
|
-
const appointmentSnap = await getDoc(appointmentRef);
|
|
330
|
-
|
|
331
|
-
if (!appointmentSnap.exists()) {
|
|
332
|
-
console.warn(
|
|
333
|
-
`[FormInit] Appointment ${appointmentId} not found when removing forms for procedure ${procedureId}.`
|
|
334
|
-
);
|
|
335
|
-
return removedFormIds;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const appointment = appointmentSnap.data() as any;
|
|
339
|
-
const linkedForms = appointment.linkedForms || [];
|
|
340
|
-
|
|
341
|
-
// Get all procedure IDs (main + extended) to check if shared forms are still referenced
|
|
342
|
-
const mainProcedureId = appointment.procedureId;
|
|
343
|
-
const extendedProcedureIds = appointment.metadata?.extendedProcedures?.map(
|
|
344
|
-
(ep: any) => ep.procedureId
|
|
345
|
-
) || [];
|
|
346
|
-
const allProcedureIds = [mainProcedureId, ...extendedProcedureIds].filter(Boolean);
|
|
347
|
-
const remainingProcedureIds = allProcedureIds.filter((id: string) => id !== procedureId);
|
|
348
|
-
|
|
349
|
-
// Helper function to check if form appears multiple times in linkedForms (indicating it's shared)
|
|
350
|
-
const isFormSharedAndReferenced = (formId: string): boolean => {
|
|
351
|
-
const formEntries = linkedForms.filter((form: LinkedFormInfo) => form.formId === formId);
|
|
352
|
-
// If form appears multiple times in linkedForms, it's shared across procedures
|
|
353
|
-
return formEntries.length > 1;
|
|
354
|
-
};
|
|
355
|
-
|
|
356
|
-
// Remove from doctor forms subcollection
|
|
357
|
-
const doctorFormsRef = collection(appointmentRef, DOCTOR_FORMS_SUBCOLLECTION);
|
|
358
|
-
const doctorFormsQuery = query(
|
|
359
|
-
doctorFormsRef,
|
|
360
|
-
where('procedureId', '==', procedureId)
|
|
361
|
-
);
|
|
362
|
-
const doctorFormsSnap = await getDocs(doctorFormsQuery);
|
|
363
|
-
|
|
364
|
-
for (const formDoc of doctorFormsSnap.docs) {
|
|
365
|
-
try {
|
|
366
|
-
const formData = formDoc.data() as FilledDocument;
|
|
367
|
-
|
|
368
|
-
// Fetch template to check if form is shared
|
|
369
|
-
let isShared = false;
|
|
370
|
-
if (formData.templateId) {
|
|
371
|
-
try {
|
|
372
|
-
const templateDoc = doc(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
|
|
373
|
-
const templateSnap = await getDoc(templateDoc);
|
|
374
|
-
if (templateSnap.exists()) {
|
|
375
|
-
const template = templateSnap.data() as DocumentTemplate;
|
|
376
|
-
isShared = !isProcedureSpecificForm(template);
|
|
377
|
-
}
|
|
378
|
-
} catch (error) {
|
|
379
|
-
console.warn(
|
|
380
|
-
`[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
|
|
381
|
-
error
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Only delete procedure-specific forms
|
|
387
|
-
// Shared forms are kept even if procedureId matches, as they may be used by other procedures
|
|
388
|
-
if (!isShared) {
|
|
389
|
-
await deleteDoc(formDoc.ref);
|
|
390
|
-
removedFormIds.push(formDoc.id);
|
|
391
|
-
console.log(
|
|
392
|
-
`[FormInit] Removed procedure-specific doctor form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
|
|
393
|
-
);
|
|
394
|
-
} else {
|
|
395
|
-
// Check if form is still referenced by other procedures (appears multiple times in linkedForms)
|
|
396
|
-
if (isFormSharedAndReferenced(formDoc.id)) {
|
|
397
|
-
console.log(
|
|
398
|
-
`[FormInit] Skipped deletion of shared doctor form ${formDoc.id} - still referenced by other procedures.`
|
|
399
|
-
);
|
|
400
|
-
} else {
|
|
401
|
-
// Shared form but only referenced by this procedure (shouldn't happen, but handle it)
|
|
402
|
-
await deleteDoc(formDoc.ref);
|
|
403
|
-
removedFormIds.push(formDoc.id);
|
|
404
|
-
console.log(
|
|
405
|
-
`[FormInit] Removed shared doctor form ${formDoc.id} - no longer referenced by other procedures.`
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
} catch (error) {
|
|
410
|
-
console.error(
|
|
411
|
-
`[FormInit] Error removing doctor form ${formDoc.id}:`,
|
|
412
|
-
error
|
|
413
|
-
);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Remove from user forms subcollection
|
|
418
|
-
const userFormsRef = collection(appointmentRef, USER_FORMS_SUBCOLLECTION);
|
|
419
|
-
const userFormsQuery = query(
|
|
420
|
-
userFormsRef,
|
|
421
|
-
where('procedureId', '==', procedureId)
|
|
422
|
-
);
|
|
423
|
-
const userFormsSnap = await getDocs(userFormsQuery);
|
|
424
|
-
|
|
425
|
-
for (const formDoc of userFormsSnap.docs) {
|
|
426
|
-
try {
|
|
427
|
-
const formData = formDoc.data() as FilledDocument;
|
|
428
|
-
|
|
429
|
-
// Fetch template to check if form is shared
|
|
430
|
-
let isShared = false;
|
|
431
|
-
if (formData.templateId) {
|
|
432
|
-
try {
|
|
433
|
-
const templateDoc = doc(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
|
|
434
|
-
const templateSnap = await getDoc(templateDoc);
|
|
435
|
-
if (templateSnap.exists()) {
|
|
436
|
-
const template = templateSnap.data() as DocumentTemplate;
|
|
437
|
-
isShared = !isProcedureSpecificForm(template);
|
|
438
|
-
}
|
|
439
|
-
} catch (error) {
|
|
440
|
-
console.warn(
|
|
441
|
-
`[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
|
|
442
|
-
error
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Only delete procedure-specific forms
|
|
448
|
-
// Shared forms are kept even if procedureId matches, as they may be used by other procedures
|
|
449
|
-
if (!isShared) {
|
|
450
|
-
await deleteDoc(formDoc.ref);
|
|
451
|
-
removedFormIds.push(formDoc.id);
|
|
452
|
-
console.log(
|
|
453
|
-
`[FormInit] Removed procedure-specific user form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
|
|
454
|
-
);
|
|
455
|
-
} else {
|
|
456
|
-
// Check if form is still referenced by other procedures (appears multiple times in linkedForms)
|
|
457
|
-
if (isFormSharedAndReferenced(formDoc.id)) {
|
|
458
|
-
console.log(
|
|
459
|
-
`[FormInit] Skipped deletion of shared user form ${formDoc.id} - still referenced by other procedures.`
|
|
460
|
-
);
|
|
461
|
-
} else {
|
|
462
|
-
// Shared form but only referenced by this procedure (shouldn't happen, but handle it)
|
|
463
|
-
await deleteDoc(formDoc.ref);
|
|
464
|
-
removedFormIds.push(formDoc.id);
|
|
465
|
-
console.log(
|
|
466
|
-
`[FormInit] Removed shared user form ${formDoc.id} - no longer referenced by other procedures.`
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
} catch (error) {
|
|
471
|
-
console.error(
|
|
472
|
-
`[FormInit] Error removing user form ${formDoc.id}:`,
|
|
473
|
-
error
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return removedFormIds;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Gets all forms associated with a specific procedure in an appointment
|
|
483
|
-
* @param db Firestore instance
|
|
484
|
-
* @param appointmentId Appointment ID
|
|
485
|
-
* @param procedureId Procedure ID
|
|
486
|
-
* @returns Array of form IDs
|
|
487
|
-
*/
|
|
488
|
-
export async function getFormsForExtendedProcedure(
|
|
489
|
-
db: Firestore,
|
|
490
|
-
appointmentId: string,
|
|
491
|
-
procedureId: string
|
|
492
|
-
): Promise<string[]> {
|
|
493
|
-
const formIds: string[] = [];
|
|
494
|
-
|
|
495
|
-
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
496
|
-
|
|
497
|
-
// Query doctor forms for this procedure
|
|
498
|
-
const doctorFormsRef = collection(appointmentRef, DOCTOR_FORMS_SUBCOLLECTION);
|
|
499
|
-
const doctorFormsQuery = query(
|
|
500
|
-
doctorFormsRef,
|
|
501
|
-
where('procedureId', '==', procedureId)
|
|
502
|
-
);
|
|
503
|
-
const doctorFormsSnap = await getDocs(doctorFormsQuery);
|
|
504
|
-
doctorFormsSnap.docs.forEach(doc => formIds.push(doc.id));
|
|
505
|
-
|
|
506
|
-
// Query user forms for this procedure
|
|
507
|
-
const userFormsRef = collection(appointmentRef, USER_FORMS_SUBCOLLECTION);
|
|
508
|
-
const userFormsQuery = query(
|
|
509
|
-
userFormsRef,
|
|
510
|
-
where('procedureId', '==', procedureId)
|
|
511
|
-
);
|
|
512
|
-
const userFormsSnap = await getDocs(userFormsQuery);
|
|
513
|
-
userFormsSnap.docs.forEach(doc => formIds.push(doc.id));
|
|
514
|
-
|
|
515
|
-
return formIds;
|
|
516
|
-
}
|
|
1
|
+
import { Firestore, collection, doc, addDoc, deleteDoc, getDocs, query, where, serverTimestamp, getDoc, updateDoc } from 'firebase/firestore';
|
|
2
|
+
import {
|
|
3
|
+
DocumentTemplate,
|
|
4
|
+
FilledDocumentStatus,
|
|
5
|
+
DOCTOR_FORMS_SUBCOLLECTION,
|
|
6
|
+
USER_FORMS_SUBCOLLECTION,
|
|
7
|
+
DOCUMENTATION_TEMPLATES_COLLECTION,
|
|
8
|
+
FilledDocument,
|
|
9
|
+
} from '../../../types/documentation-templates';
|
|
10
|
+
import {
|
|
11
|
+
APPOINTMENTS_COLLECTION,
|
|
12
|
+
LinkedFormInfo,
|
|
13
|
+
} from '../../../types/appointment';
|
|
14
|
+
import { TechnologyDocumentationTemplate } from '../../../backoffice/types/technology.types';
|
|
15
|
+
|
|
16
|
+
export interface InitializeExtendedProcedureFormsResult {
|
|
17
|
+
initializedFormsInfo: LinkedFormInfo[];
|
|
18
|
+
pendingUserFormsIds: string[];
|
|
19
|
+
allLinkedFormIds: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Determines if a form template is procedure-specific or general/shared
|
|
24
|
+
* Uses explicit tags first, then falls back to heuristics
|
|
25
|
+
*
|
|
26
|
+
* Priority order:
|
|
27
|
+
* 1. "procedure-specific" tag → always procedure-specific
|
|
28
|
+
* 2. "shared" tag → always shared
|
|
29
|
+
* 3. Consent-related tags ("consent", "consent-form") → procedure-specific
|
|
30
|
+
* 4. Title contains "consent" → procedure-specific
|
|
31
|
+
* 5. Default → shared (general forms)
|
|
32
|
+
*
|
|
33
|
+
* @param template DocumentTemplate to check
|
|
34
|
+
* @returns true if procedure-specific, false if general/shared
|
|
35
|
+
*/
|
|
36
|
+
function isProcedureSpecificForm(template: DocumentTemplate): boolean {
|
|
37
|
+
const tags = template.tags || [];
|
|
38
|
+
const titleLower = template.title.toLowerCase();
|
|
39
|
+
|
|
40
|
+
// Priority 1: Explicit "procedure-specific" tag → always procedure-specific
|
|
41
|
+
if (tags.includes('procedure-specific')) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Priority 2: Explicit "shared" tag → always shared
|
|
46
|
+
if (tags.includes('shared')) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Priority 3: Consent-related tags → procedure-specific
|
|
51
|
+
if (tags.some(tag => {
|
|
52
|
+
const tagLower = tag.toLowerCase();
|
|
53
|
+
return tagLower.includes('consent') || tagLower === 'consent-form';
|
|
54
|
+
})) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Priority 4: Check title for "consent" → procedure-specific
|
|
59
|
+
if (titleLower.includes('consent')) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Priority 5: Default → shared (general forms)
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Checks if a form with the given templateId already exists in the appointment
|
|
69
|
+
* @param db Firestore instance
|
|
70
|
+
* @param appointmentId Appointment ID
|
|
71
|
+
* @param templateId Template ID to check
|
|
72
|
+
* @param isUserForm Whether to check user-forms or doctor-forms subcollection
|
|
73
|
+
* @returns Existing FilledDocument or null if not found
|
|
74
|
+
*/
|
|
75
|
+
async function findExistingFormByTemplate(
|
|
76
|
+
db: Firestore,
|
|
77
|
+
appointmentId: string,
|
|
78
|
+
templateId: string,
|
|
79
|
+
isUserForm: boolean
|
|
80
|
+
): Promise<FilledDocument | null> {
|
|
81
|
+
const formSubcollection = isUserForm
|
|
82
|
+
? USER_FORMS_SUBCOLLECTION
|
|
83
|
+
: DOCTOR_FORMS_SUBCOLLECTION;
|
|
84
|
+
|
|
85
|
+
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
86
|
+
const formsCollectionRef = collection(appointmentRef, formSubcollection);
|
|
87
|
+
|
|
88
|
+
const q = query(
|
|
89
|
+
formsCollectionRef,
|
|
90
|
+
where('templateId', '==', templateId)
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const querySnapshot = await getDocs(q);
|
|
94
|
+
|
|
95
|
+
if (querySnapshot.empty) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Return the first matching form (should only be one for general forms)
|
|
100
|
+
const docSnap = querySnapshot.docs[0];
|
|
101
|
+
const data = docSnap.data() as FilledDocument;
|
|
102
|
+
|
|
103
|
+
// Ensure id is populated from Firestore document ID if not in data
|
|
104
|
+
if (!data.id) {
|
|
105
|
+
data.id = docSnap.id;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Initializes forms for an extended procedure using client-side Firestore
|
|
113
|
+
* Similar to DocumentManagerAdminService but for client-side usage
|
|
114
|
+
* @param db Firestore instance
|
|
115
|
+
* @param appointmentId Appointment ID
|
|
116
|
+
* @param procedureId Procedure ID for forms
|
|
117
|
+
* @param technologyTemplates Technology documentation templates
|
|
118
|
+
* @param patientId Patient ID
|
|
119
|
+
* @param practitionerId Practitioner ID
|
|
120
|
+
* @param clinicId Clinic ID
|
|
121
|
+
* @returns Form initialization result
|
|
122
|
+
*/
|
|
123
|
+
export async function initializeFormsForExtendedProcedure(
|
|
124
|
+
db: Firestore,
|
|
125
|
+
appointmentId: string,
|
|
126
|
+
procedureId: string,
|
|
127
|
+
technologyTemplates: TechnologyDocumentationTemplate[],
|
|
128
|
+
patientId: string,
|
|
129
|
+
practitionerId: string,
|
|
130
|
+
clinicId: string
|
|
131
|
+
): Promise<InitializeExtendedProcedureFormsResult> {
|
|
132
|
+
const initializedFormsInfo: LinkedFormInfo[] = [];
|
|
133
|
+
const pendingUserFormsIds: string[] = [];
|
|
134
|
+
const allLinkedFormIds: string[] = [];
|
|
135
|
+
|
|
136
|
+
if (!technologyTemplates || technologyTemplates.length === 0) {
|
|
137
|
+
console.log(
|
|
138
|
+
`[FormInit] No document templates to initialize for extended procedure ${procedureId} in appointment ${appointmentId}.`
|
|
139
|
+
);
|
|
140
|
+
return {
|
|
141
|
+
initializedFormsInfo,
|
|
142
|
+
pendingUserFormsIds,
|
|
143
|
+
allLinkedFormIds,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Fetch all template documents
|
|
148
|
+
const templateIds = technologyTemplates.map((t) => t.templateId);
|
|
149
|
+
|
|
150
|
+
// Note: Client-side Firestore doesn't support 'in' queries with more than 10 items
|
|
151
|
+
// We'll fetch templates one by one for now (can be optimized later)
|
|
152
|
+
const templatesMap = new Map<string, DocumentTemplate>();
|
|
153
|
+
|
|
154
|
+
for (const templateId of templateIds) {
|
|
155
|
+
try {
|
|
156
|
+
const templateDoc = doc(db, DOCUMENTATION_TEMPLATES_COLLECTION, templateId);
|
|
157
|
+
const templateSnap = await getDoc(templateDoc);
|
|
158
|
+
|
|
159
|
+
if (templateSnap.exists()) {
|
|
160
|
+
templatesMap.set(templateId, templateSnap.data() as DocumentTemplate);
|
|
161
|
+
} else {
|
|
162
|
+
console.warn(
|
|
163
|
+
`[FormInit] Template ${templateId} not found in Firestore.`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error(`[FormInit] Error fetching template ${templateId}:`, error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Initialize forms for each template (both user forms and doctor forms for extended procedures)
|
|
172
|
+
for (const templateRef of technologyTemplates) {
|
|
173
|
+
const template = templatesMap.get(templateRef.templateId);
|
|
174
|
+
if (!template) {
|
|
175
|
+
console.warn(
|
|
176
|
+
`[FormInit] Template ${templateRef.templateId} not found in Firestore.`
|
|
177
|
+
);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const isRequired = templateRef.isRequired;
|
|
182
|
+
const isUserForm = templateRef.isUserForm || false;
|
|
183
|
+
const formSubcollectionPath = isUserForm
|
|
184
|
+
? USER_FORMS_SUBCOLLECTION
|
|
185
|
+
: DOCTOR_FORMS_SUBCOLLECTION;
|
|
186
|
+
|
|
187
|
+
// Check if form is procedure-specific or general/shared
|
|
188
|
+
const isProcedureSpecific = isProcedureSpecificForm(template);
|
|
189
|
+
|
|
190
|
+
// For general/shared forms, check if form already exists in appointment
|
|
191
|
+
let existingForm: FilledDocument | null = null;
|
|
192
|
+
if (!isProcedureSpecific) {
|
|
193
|
+
try {
|
|
194
|
+
existingForm = await findExistingFormByTemplate(
|
|
195
|
+
db,
|
|
196
|
+
appointmentId,
|
|
197
|
+
templateRef.templateId,
|
|
198
|
+
isUserForm
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (existingForm) {
|
|
202
|
+
console.log(
|
|
203
|
+
`[FormInit] Found existing shared form ${existingForm.id} (template: ${template.id}) for appointment ${appointmentId}. Reusing instead of creating duplicate.`
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// Reuse existing form - add to linkedForms but don't create new document
|
|
207
|
+
// Note: We still add it to allLinkedFormIds to ensure it's tracked,
|
|
208
|
+
// but we don't add it again if it's already in the appointment's linkedForms
|
|
209
|
+
// (This will be handled by the caller merging the arrays)
|
|
210
|
+
|
|
211
|
+
const linkedForm: LinkedFormInfo = {
|
|
212
|
+
formId: existingForm.id,
|
|
213
|
+
templateId: template.id,
|
|
214
|
+
templateVersion: template.version,
|
|
215
|
+
title: template.title,
|
|
216
|
+
isUserForm: isUserForm,
|
|
217
|
+
isRequired: isRequired,
|
|
218
|
+
sortingOrder: templateRef.sortingOrder,
|
|
219
|
+
status: existingForm.status || FilledDocumentStatus.PENDING,
|
|
220
|
+
path: `${APPOINTMENTS_COLLECTION}/${appointmentId}/${formSubcollectionPath}/${existingForm.id}`,
|
|
221
|
+
procedureId: procedureId, // Track which procedure this form belongs to
|
|
222
|
+
};
|
|
223
|
+
initializedFormsInfo.push(linkedForm);
|
|
224
|
+
|
|
225
|
+
// Add to allLinkedFormIds if not already present (to avoid duplicates)
|
|
226
|
+
if (!allLinkedFormIds.includes(existingForm.id)) {
|
|
227
|
+
allLinkedFormIds.push(existingForm.id);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Add to pendingUserFormsIds if it's a required user form and not already completed
|
|
231
|
+
if (isUserForm && isRequired && existingForm.status === FilledDocumentStatus.PENDING) {
|
|
232
|
+
if (!pendingUserFormsIds.includes(existingForm.id)) {
|
|
233
|
+
pendingUserFormsIds.push(existingForm.id);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
continue; // Skip creating new form, reuse existing one
|
|
238
|
+
}
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.warn(
|
|
241
|
+
`[FormInit] Error checking for existing form (template: ${templateRef.templateId}):`,
|
|
242
|
+
error
|
|
243
|
+
);
|
|
244
|
+
// Continue to create new form if check fails
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Create new form document (either procedure-specific or general form not found)
|
|
249
|
+
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
250
|
+
const formsCollectionRef = collection(appointmentRef, formSubcollectionPath);
|
|
251
|
+
|
|
252
|
+
const filledDocumentData = {
|
|
253
|
+
templateId: templateRef.templateId,
|
|
254
|
+
templateVersion: template.version,
|
|
255
|
+
isUserForm: isUserForm,
|
|
256
|
+
isRequired: isRequired,
|
|
257
|
+
appointmentId: appointmentId,
|
|
258
|
+
procedureId: procedureId,
|
|
259
|
+
patientId: patientId,
|
|
260
|
+
practitionerId: practitionerId,
|
|
261
|
+
clinicId: clinicId,
|
|
262
|
+
createdAt: serverTimestamp(),
|
|
263
|
+
updatedAt: serverTimestamp(),
|
|
264
|
+
values: {},
|
|
265
|
+
status: FilledDocumentStatus.PENDING,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const docRef = await addDoc(formsCollectionRef, filledDocumentData);
|
|
270
|
+
const filledDocumentId = docRef.id;
|
|
271
|
+
|
|
272
|
+
// Update the document to include the id field in the data (for consistency)
|
|
273
|
+
await updateDoc(docRef, { id: filledDocumentId });
|
|
274
|
+
|
|
275
|
+
// Add to pendingUserFormsIds if it's a required user form
|
|
276
|
+
if (isUserForm && isRequired) {
|
|
277
|
+
pendingUserFormsIds.push(filledDocumentId);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
allLinkedFormIds.push(filledDocumentId);
|
|
281
|
+
|
|
282
|
+
const linkedForm: LinkedFormInfo = {
|
|
283
|
+
formId: filledDocumentId,
|
|
284
|
+
templateId: template.id,
|
|
285
|
+
templateVersion: template.version,
|
|
286
|
+
title: template.title,
|
|
287
|
+
isUserForm: isUserForm,
|
|
288
|
+
isRequired: isRequired,
|
|
289
|
+
sortingOrder: templateRef.sortingOrder,
|
|
290
|
+
status: FilledDocumentStatus.PENDING,
|
|
291
|
+
path: docRef.path,
|
|
292
|
+
procedureId: procedureId, // Track which procedure this form belongs to
|
|
293
|
+
};
|
|
294
|
+
initializedFormsInfo.push(linkedForm);
|
|
295
|
+
|
|
296
|
+
const formType = isProcedureSpecific ? 'procedure-specific' : 'general/shared';
|
|
297
|
+
console.log(
|
|
298
|
+
`[FormInit] Created ${formType} FilledDocument ${filledDocumentId} (template: ${template.id}, isUserForm: ${isUserForm}) for extended procedure ${procedureId} in appointment ${appointmentId}.`
|
|
299
|
+
);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error(
|
|
302
|
+
`[FormInit] Error creating form for template ${templateRef.templateId}:`,
|
|
303
|
+
error
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return { initializedFormsInfo, pendingUserFormsIds, allLinkedFormIds };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Removes all forms associated with a specific procedure from an appointment
|
|
313
|
+
* Only removes procedure-specific forms. Shared forms are kept if still referenced by other procedures.
|
|
314
|
+
* Removes both user forms and doctor forms
|
|
315
|
+
* @param db Firestore instance
|
|
316
|
+
* @param appointmentId Appointment ID
|
|
317
|
+
* @param procedureId Procedure ID to remove forms for
|
|
318
|
+
* @returns Array of removed form IDs
|
|
319
|
+
*/
|
|
320
|
+
export async function removeFormsForExtendedProcedure(
|
|
321
|
+
db: Firestore,
|
|
322
|
+
appointmentId: string,
|
|
323
|
+
procedureId: string
|
|
324
|
+
): Promise<string[]> {
|
|
325
|
+
const removedFormIds: string[] = [];
|
|
326
|
+
|
|
327
|
+
// Get appointment to check linkedForms
|
|
328
|
+
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
329
|
+
const appointmentSnap = await getDoc(appointmentRef);
|
|
330
|
+
|
|
331
|
+
if (!appointmentSnap.exists()) {
|
|
332
|
+
console.warn(
|
|
333
|
+
`[FormInit] Appointment ${appointmentId} not found when removing forms for procedure ${procedureId}.`
|
|
334
|
+
);
|
|
335
|
+
return removedFormIds;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const appointment = appointmentSnap.data() as any;
|
|
339
|
+
const linkedForms = appointment.linkedForms || [];
|
|
340
|
+
|
|
341
|
+
// Get all procedure IDs (main + extended) to check if shared forms are still referenced
|
|
342
|
+
const mainProcedureId = appointment.procedureId;
|
|
343
|
+
const extendedProcedureIds = appointment.metadata?.extendedProcedures?.map(
|
|
344
|
+
(ep: any) => ep.procedureId
|
|
345
|
+
) || [];
|
|
346
|
+
const allProcedureIds = [mainProcedureId, ...extendedProcedureIds].filter(Boolean);
|
|
347
|
+
const remainingProcedureIds = allProcedureIds.filter((id: string) => id !== procedureId);
|
|
348
|
+
|
|
349
|
+
// Helper function to check if form appears multiple times in linkedForms (indicating it's shared)
|
|
350
|
+
const isFormSharedAndReferenced = (formId: string): boolean => {
|
|
351
|
+
const formEntries = linkedForms.filter((form: LinkedFormInfo) => form.formId === formId);
|
|
352
|
+
// If form appears multiple times in linkedForms, it's shared across procedures
|
|
353
|
+
return formEntries.length > 1;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// Remove from doctor forms subcollection
|
|
357
|
+
const doctorFormsRef = collection(appointmentRef, DOCTOR_FORMS_SUBCOLLECTION);
|
|
358
|
+
const doctorFormsQuery = query(
|
|
359
|
+
doctorFormsRef,
|
|
360
|
+
where('procedureId', '==', procedureId)
|
|
361
|
+
);
|
|
362
|
+
const doctorFormsSnap = await getDocs(doctorFormsQuery);
|
|
363
|
+
|
|
364
|
+
for (const formDoc of doctorFormsSnap.docs) {
|
|
365
|
+
try {
|
|
366
|
+
const formData = formDoc.data() as FilledDocument;
|
|
367
|
+
|
|
368
|
+
// Fetch template to check if form is shared
|
|
369
|
+
let isShared = false;
|
|
370
|
+
if (formData.templateId) {
|
|
371
|
+
try {
|
|
372
|
+
const templateDoc = doc(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
|
|
373
|
+
const templateSnap = await getDoc(templateDoc);
|
|
374
|
+
if (templateSnap.exists()) {
|
|
375
|
+
const template = templateSnap.data() as DocumentTemplate;
|
|
376
|
+
isShared = !isProcedureSpecificForm(template);
|
|
377
|
+
}
|
|
378
|
+
} catch (error) {
|
|
379
|
+
console.warn(
|
|
380
|
+
`[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
|
|
381
|
+
error
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Only delete procedure-specific forms
|
|
387
|
+
// Shared forms are kept even if procedureId matches, as they may be used by other procedures
|
|
388
|
+
if (!isShared) {
|
|
389
|
+
await deleteDoc(formDoc.ref);
|
|
390
|
+
removedFormIds.push(formDoc.id);
|
|
391
|
+
console.log(
|
|
392
|
+
`[FormInit] Removed procedure-specific doctor form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
|
|
393
|
+
);
|
|
394
|
+
} else {
|
|
395
|
+
// Check if form is still referenced by other procedures (appears multiple times in linkedForms)
|
|
396
|
+
if (isFormSharedAndReferenced(formDoc.id)) {
|
|
397
|
+
console.log(
|
|
398
|
+
`[FormInit] Skipped deletion of shared doctor form ${formDoc.id} - still referenced by other procedures.`
|
|
399
|
+
);
|
|
400
|
+
} else {
|
|
401
|
+
// Shared form but only referenced by this procedure (shouldn't happen, but handle it)
|
|
402
|
+
await deleteDoc(formDoc.ref);
|
|
403
|
+
removedFormIds.push(formDoc.id);
|
|
404
|
+
console.log(
|
|
405
|
+
`[FormInit] Removed shared doctor form ${formDoc.id} - no longer referenced by other procedures.`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error(
|
|
411
|
+
`[FormInit] Error removing doctor form ${formDoc.id}:`,
|
|
412
|
+
error
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Remove from user forms subcollection
|
|
418
|
+
const userFormsRef = collection(appointmentRef, USER_FORMS_SUBCOLLECTION);
|
|
419
|
+
const userFormsQuery = query(
|
|
420
|
+
userFormsRef,
|
|
421
|
+
where('procedureId', '==', procedureId)
|
|
422
|
+
);
|
|
423
|
+
const userFormsSnap = await getDocs(userFormsQuery);
|
|
424
|
+
|
|
425
|
+
for (const formDoc of userFormsSnap.docs) {
|
|
426
|
+
try {
|
|
427
|
+
const formData = formDoc.data() as FilledDocument;
|
|
428
|
+
|
|
429
|
+
// Fetch template to check if form is shared
|
|
430
|
+
let isShared = false;
|
|
431
|
+
if (formData.templateId) {
|
|
432
|
+
try {
|
|
433
|
+
const templateDoc = doc(db, DOCUMENTATION_TEMPLATES_COLLECTION, formData.templateId);
|
|
434
|
+
const templateSnap = await getDoc(templateDoc);
|
|
435
|
+
if (templateSnap.exists()) {
|
|
436
|
+
const template = templateSnap.data() as DocumentTemplate;
|
|
437
|
+
isShared = !isProcedureSpecificForm(template);
|
|
438
|
+
}
|
|
439
|
+
} catch (error) {
|
|
440
|
+
console.warn(
|
|
441
|
+
`[FormInit] Could not check template for form ${formDoc.id}, assuming procedure-specific:`,
|
|
442
|
+
error
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Only delete procedure-specific forms
|
|
448
|
+
// Shared forms are kept even if procedureId matches, as they may be used by other procedures
|
|
449
|
+
if (!isShared) {
|
|
450
|
+
await deleteDoc(formDoc.ref);
|
|
451
|
+
removedFormIds.push(formDoc.id);
|
|
452
|
+
console.log(
|
|
453
|
+
`[FormInit] Removed procedure-specific user form ${formDoc.id} for extended procedure ${procedureId} from appointment ${appointmentId}.`
|
|
454
|
+
);
|
|
455
|
+
} else {
|
|
456
|
+
// Check if form is still referenced by other procedures (appears multiple times in linkedForms)
|
|
457
|
+
if (isFormSharedAndReferenced(formDoc.id)) {
|
|
458
|
+
console.log(
|
|
459
|
+
`[FormInit] Skipped deletion of shared user form ${formDoc.id} - still referenced by other procedures.`
|
|
460
|
+
);
|
|
461
|
+
} else {
|
|
462
|
+
// Shared form but only referenced by this procedure (shouldn't happen, but handle it)
|
|
463
|
+
await deleteDoc(formDoc.ref);
|
|
464
|
+
removedFormIds.push(formDoc.id);
|
|
465
|
+
console.log(
|
|
466
|
+
`[FormInit] Removed shared user form ${formDoc.id} - no longer referenced by other procedures.`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
} catch (error) {
|
|
471
|
+
console.error(
|
|
472
|
+
`[FormInit] Error removing user form ${formDoc.id}:`,
|
|
473
|
+
error
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return removedFormIds;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Gets all forms associated with a specific procedure in an appointment
|
|
483
|
+
* @param db Firestore instance
|
|
484
|
+
* @param appointmentId Appointment ID
|
|
485
|
+
* @param procedureId Procedure ID
|
|
486
|
+
* @returns Array of form IDs
|
|
487
|
+
*/
|
|
488
|
+
export async function getFormsForExtendedProcedure(
|
|
489
|
+
db: Firestore,
|
|
490
|
+
appointmentId: string,
|
|
491
|
+
procedureId: string
|
|
492
|
+
): Promise<string[]> {
|
|
493
|
+
const formIds: string[] = [];
|
|
494
|
+
|
|
495
|
+
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
496
|
+
|
|
497
|
+
// Query doctor forms for this procedure
|
|
498
|
+
const doctorFormsRef = collection(appointmentRef, DOCTOR_FORMS_SUBCOLLECTION);
|
|
499
|
+
const doctorFormsQuery = query(
|
|
500
|
+
doctorFormsRef,
|
|
501
|
+
where('procedureId', '==', procedureId)
|
|
502
|
+
);
|
|
503
|
+
const doctorFormsSnap = await getDocs(doctorFormsQuery);
|
|
504
|
+
doctorFormsSnap.docs.forEach(doc => formIds.push(doc.id));
|
|
505
|
+
|
|
506
|
+
// Query user forms for this procedure
|
|
507
|
+
const userFormsRef = collection(appointmentRef, USER_FORMS_SUBCOLLECTION);
|
|
508
|
+
const userFormsQuery = query(
|
|
509
|
+
userFormsRef,
|
|
510
|
+
where('procedureId', '==', procedureId)
|
|
511
|
+
);
|
|
512
|
+
const userFormsSnap = await getDocs(userFormsQuery);
|
|
513
|
+
userFormsSnap.docs.forEach(doc => formIds.push(doc.id));
|
|
514
|
+
|
|
515
|
+
return formIds;
|
|
516
|
+
}
|