@blackcode_sa/metaestetics-api 1.15.14 → 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 +297 -9
- package/dist/index.d.ts +297 -9
- package/dist/index.js +1144 -632
- package/dist/index.mjs +1139 -619
- 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,302 +1,302 @@
|
|
|
1
|
-
import { Firestore, updateDoc, serverTimestamp, doc, Timestamp } from 'firebase/firestore';
|
|
2
|
-
import {
|
|
3
|
-
Appointment,
|
|
4
|
-
BeforeAfterPerZone,
|
|
5
|
-
APPOINTMENTS_COLLECTION,
|
|
6
|
-
} from '../../../types/appointment';
|
|
7
|
-
import { getAppointmentOrThrow } from './zone-management.utils';
|
|
8
|
-
import { MediaResource } from '../../media/media.service';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Updates visibility fields with audit trail
|
|
12
|
-
* Note: We use Timestamp.now() instead of serverTimestamp() because Firestore
|
|
13
|
-
* doesn't support serverTimestamp() inside arrays. The timestamp will be set
|
|
14
|
-
* on the client side, which is acceptable for audit purposes.
|
|
15
|
-
* @param updates Partial updates object
|
|
16
|
-
* @param doctorId ID of the doctor making the change
|
|
17
|
-
*/
|
|
18
|
-
function addVisibilityAudit(
|
|
19
|
-
updates: Partial<BeforeAfterPerZone>,
|
|
20
|
-
doctorId: string,
|
|
21
|
-
): Partial<BeforeAfterPerZone> {
|
|
22
|
-
// Only add audit fields if visibility-related fields are being updated
|
|
23
|
-
if (
|
|
24
|
-
updates.showToPatient !== undefined ||
|
|
25
|
-
updates.beforeNoteVisibleToPatient !== undefined ||
|
|
26
|
-
updates.afterNoteVisibleToPatient !== undefined
|
|
27
|
-
) {
|
|
28
|
-
return {
|
|
29
|
-
...updates,
|
|
30
|
-
visibilityUpdatedAt: Timestamp.now(), // Use client-side timestamp (Firestore limitation)
|
|
31
|
-
visibilityUpdatedBy: doctorId,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
return updates;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Updates a specific photo entry in a zone by index
|
|
39
|
-
* Can update before/after photos and their notes
|
|
40
|
-
*
|
|
41
|
-
* @param db Firestore instance
|
|
42
|
-
* @param appointmentId Appointment ID
|
|
43
|
-
* @param zoneId Zone ID
|
|
44
|
-
* @param photoIndex Index of the photo entry to update
|
|
45
|
-
* @param updates Partial updates to apply
|
|
46
|
-
* @param doctorId Optional doctor ID for audit trail (required if updating visibility)
|
|
47
|
-
* @returns Updated appointment
|
|
48
|
-
*/
|
|
49
|
-
export async function updateZonePhotoEntryUtil(
|
|
50
|
-
db: Firestore,
|
|
51
|
-
appointmentId: string,
|
|
52
|
-
zoneId: string,
|
|
53
|
-
photoIndex: number,
|
|
54
|
-
updates: Partial<BeforeAfterPerZone>,
|
|
55
|
-
doctorId?: string,
|
|
56
|
-
): Promise<Appointment> {
|
|
57
|
-
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
58
|
-
|
|
59
|
-
const zonePhotos = appointment.metadata?.zonePhotos as
|
|
60
|
-
| Record<string, BeforeAfterPerZone[]>
|
|
61
|
-
| undefined
|
|
62
|
-
| null;
|
|
63
|
-
|
|
64
|
-
if (!zonePhotos || !zonePhotos[zoneId] || !Array.isArray(zonePhotos[zoneId])) {
|
|
65
|
-
throw new Error(`No photos found for zone ${zoneId} in appointment ${appointmentId}`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const zoneArray = zonePhotos[zoneId];
|
|
69
|
-
if (photoIndex < 0 || photoIndex >= zoneArray.length) {
|
|
70
|
-
throw new Error(`Invalid photo index ${photoIndex} for zone ${zoneId}. Must be between 0 and ${zoneArray.length - 1}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Update the entry with audit trail if visibility is being changed
|
|
74
|
-
const updatesWithAudit = doctorId
|
|
75
|
-
? addVisibilityAudit(updates, doctorId)
|
|
76
|
-
: updates;
|
|
77
|
-
|
|
78
|
-
const updatedZonePhotos = { ...zonePhotos };
|
|
79
|
-
updatedZonePhotos[zoneId] = [...zoneArray];
|
|
80
|
-
updatedZonePhotos[zoneId][photoIndex] = {
|
|
81
|
-
...zoneArray[photoIndex],
|
|
82
|
-
...updatesWithAudit,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// Update appointment
|
|
86
|
-
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
87
|
-
await updateDoc(appointmentRef, {
|
|
88
|
-
'metadata.zonePhotos': updatedZonePhotos,
|
|
89
|
-
updatedAt: serverTimestamp(),
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
return getAppointmentOrThrow(db, appointmentId);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Adds an after photo to an existing before photo entry
|
|
97
|
-
*
|
|
98
|
-
* @param db Firestore instance
|
|
99
|
-
* @param appointmentId Appointment ID
|
|
100
|
-
* @param zoneId Zone ID
|
|
101
|
-
* @param photoIndex Index of the entry to add after photo to
|
|
102
|
-
* @param afterPhotoUrl URL of the after photo
|
|
103
|
-
* @param afterNote Optional note for the after photo
|
|
104
|
-
* @returns Updated appointment
|
|
105
|
-
*/
|
|
106
|
-
export async function addAfterPhotoToEntryUtil(
|
|
107
|
-
db: Firestore,
|
|
108
|
-
appointmentId: string,
|
|
109
|
-
zoneId: string,
|
|
110
|
-
photoIndex: number,
|
|
111
|
-
afterPhotoUrl: MediaResource,
|
|
112
|
-
afterNote?: string
|
|
113
|
-
): Promise<Appointment> {
|
|
114
|
-
return updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, {
|
|
115
|
-
after: afterPhotoUrl,
|
|
116
|
-
afterNote: afterNote || null,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Updates notes for a photo entry
|
|
122
|
-
*
|
|
123
|
-
* @param db Firestore instance
|
|
124
|
-
* @param appointmentId Appointment ID
|
|
125
|
-
* @param zoneId Zone ID
|
|
126
|
-
* @param photoIndex Index of the entry
|
|
127
|
-
* @param beforeNote Optional note for before photo
|
|
128
|
-
* @param afterNote Optional note for after photo
|
|
129
|
-
* @returns Updated appointment
|
|
130
|
-
*/
|
|
131
|
-
export async function updateZonePhotoNotesUtil(
|
|
132
|
-
db: Firestore,
|
|
133
|
-
appointmentId: string,
|
|
134
|
-
zoneId: string,
|
|
135
|
-
photoIndex: number,
|
|
136
|
-
beforeNote?: string,
|
|
137
|
-
afterNote?: string
|
|
138
|
-
): Promise<Appointment> {
|
|
139
|
-
const updates: Partial<BeforeAfterPerZone> = {};
|
|
140
|
-
|
|
141
|
-
if (beforeNote !== undefined) {
|
|
142
|
-
updates.beforeNote = beforeNote || null;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (afterNote !== undefined) {
|
|
146
|
-
updates.afterNote = afterNote || null;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, updates);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Removes an after photo from an existing entry (keeps the before photo)
|
|
154
|
-
*
|
|
155
|
-
* @param db Firestore instance
|
|
156
|
-
* @param appointmentId Appointment ID
|
|
157
|
-
* @param zoneId Zone ID
|
|
158
|
-
* @param photoIndex Index of the entry to remove after photo from
|
|
159
|
-
* @returns Updated appointment
|
|
160
|
-
*/
|
|
161
|
-
export async function removeAfterPhotoFromEntryUtil(
|
|
162
|
-
db: Firestore,
|
|
163
|
-
appointmentId: string,
|
|
164
|
-
zoneId: string,
|
|
165
|
-
photoIndex: number
|
|
166
|
-
): Promise<Appointment> {
|
|
167
|
-
return updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, {
|
|
168
|
-
after: null,
|
|
169
|
-
afterNote: null,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Gets a specific photo entry from a zone
|
|
175
|
-
*
|
|
176
|
-
* @param db Firestore instance
|
|
177
|
-
* @param appointmentId Appointment ID
|
|
178
|
-
* @param zoneId Zone ID
|
|
179
|
-
* @param photoIndex Index of the entry
|
|
180
|
-
* @returns Photo entry
|
|
181
|
-
*/
|
|
182
|
-
export async function getZonePhotoEntryUtil(
|
|
183
|
-
db: Firestore,
|
|
184
|
-
appointmentId: string,
|
|
185
|
-
zoneId: string,
|
|
186
|
-
photoIndex: number
|
|
187
|
-
): Promise<BeforeAfterPerZone> {
|
|
188
|
-
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
189
|
-
|
|
190
|
-
const zonePhotos = appointment.metadata?.zonePhotos as
|
|
191
|
-
| Record<string, BeforeAfterPerZone[]>
|
|
192
|
-
| undefined
|
|
193
|
-
| null;
|
|
194
|
-
|
|
195
|
-
if (!zonePhotos || !zonePhotos[zoneId] || !Array.isArray(zonePhotos[zoneId])) {
|
|
196
|
-
throw new Error(`No photos found for zone ${zoneId} in appointment ${appointmentId}`);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const zoneArray = zonePhotos[zoneId];
|
|
200
|
-
if (photoIndex < 0 || photoIndex >= zoneArray.length) {
|
|
201
|
-
throw new Error(`Invalid photo index ${photoIndex} for zone ${zoneId}`);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return zoneArray[photoIndex];
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Updates visibility of a photo pair (before AND after together)
|
|
209
|
-
* Note: If photo pair visibility is disabled, note visibility is automatically disabled as well
|
|
210
|
-
*
|
|
211
|
-
* @param db Firestore instance
|
|
212
|
-
* @param appointmentId Appointment ID
|
|
213
|
-
* @param zoneId Zone ID
|
|
214
|
-
* @param photoIndex Index of the photo entry
|
|
215
|
-
* @param showToPatient Whether the photo pair should be visible to patient
|
|
216
|
-
* @param doctorId ID of the doctor making the change (for audit trail)
|
|
217
|
-
* @returns Updated appointment
|
|
218
|
-
*/
|
|
219
|
-
export async function updateZonePhotoVisibilityUtil(
|
|
220
|
-
db: Firestore,
|
|
221
|
-
appointmentId: string,
|
|
222
|
-
zoneId: string,
|
|
223
|
-
photoIndex: number,
|
|
224
|
-
showToPatient: boolean,
|
|
225
|
-
doctorId: string,
|
|
226
|
-
): Promise<Appointment> {
|
|
227
|
-
const updates: Partial<BeforeAfterPerZone> = { showToPatient };
|
|
228
|
-
|
|
229
|
-
// If hiding photo pair from patient, also hide notes (notes can't be visible if photos aren't)
|
|
230
|
-
if (!showToPatient) {
|
|
231
|
-
updates.beforeNoteVisibleToPatient = false;
|
|
232
|
-
updates.afterNoteVisibleToPatient = false;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
return updateZonePhotoEntryUtil(
|
|
236
|
-
db,
|
|
237
|
-
appointmentId,
|
|
238
|
-
zoneId,
|
|
239
|
-
photoIndex,
|
|
240
|
-
updates,
|
|
241
|
-
doctorId,
|
|
242
|
-
);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Updates visibility of a photo note (before or after)
|
|
247
|
-
* Note: Notes can only be visible to patient if the photo pair itself is visible to patient
|
|
248
|
-
*
|
|
249
|
-
* @param db Firestore instance
|
|
250
|
-
* @param appointmentId Appointment ID
|
|
251
|
-
* @param zoneId Zone ID
|
|
252
|
-
* @param photoIndex Index of the photo entry
|
|
253
|
-
* @param noteType Type of note ('before' or 'after')
|
|
254
|
-
* @param visibleToPatient Whether the note should be visible to patient
|
|
255
|
-
* @param doctorId ID of the doctor making the change (for audit trail)
|
|
256
|
-
* @returns Updated appointment
|
|
257
|
-
* @throws Error if trying to make note visible when photo pair is not visible
|
|
258
|
-
*/
|
|
259
|
-
export async function updateZonePhotoNoteVisibilityUtil(
|
|
260
|
-
db: Firestore,
|
|
261
|
-
appointmentId: string,
|
|
262
|
-
zoneId: string,
|
|
263
|
-
photoIndex: number,
|
|
264
|
-
noteType: 'before' | 'after',
|
|
265
|
-
visibleToPatient: boolean,
|
|
266
|
-
doctorId: string,
|
|
267
|
-
): Promise<Appointment> {
|
|
268
|
-
// Get current appointment to check if photo pair is visible
|
|
269
|
-
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
270
|
-
|
|
271
|
-
const zonePhotos = appointment.metadata?.zonePhotos as
|
|
272
|
-
| Record<string, BeforeAfterPerZone[]>
|
|
273
|
-
| undefined
|
|
274
|
-
| null;
|
|
275
|
-
|
|
276
|
-
if (!zonePhotos || !zonePhotos[zoneId] || !Array.isArray(zonePhotos[zoneId])) {
|
|
277
|
-
throw new Error(`No photos found for zone ${zoneId} in appointment ${appointmentId}`);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const zoneArray = zonePhotos[zoneId];
|
|
281
|
-
if (photoIndex < 0 || photoIndex >= zoneArray.length) {
|
|
282
|
-
throw new Error(`Invalid photo index ${photoIndex} for zone ${zoneId}. Must be between 0 and ${zoneArray.length - 1}`);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const photoEntry = zoneArray[photoIndex];
|
|
286
|
-
|
|
287
|
-
// Validate: Notes can only be visible if photo pair is visible
|
|
288
|
-
if (visibleToPatient && !photoEntry.showToPatient) {
|
|
289
|
-
throw new Error(
|
|
290
|
-
'Cannot make note visible to patient: The photo pair must be visible to the patient first. ' +
|
|
291
|
-
'Please enable "Show to Patient" for the photo pair before making notes visible.'
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const updates: Partial<BeforeAfterPerZone> =
|
|
296
|
-
noteType === 'before'
|
|
297
|
-
? { beforeNoteVisibleToPatient: visibleToPatient }
|
|
298
|
-
: { afterNoteVisibleToPatient: visibleToPatient };
|
|
299
|
-
|
|
300
|
-
return updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, updates, doctorId);
|
|
301
|
-
}
|
|
302
|
-
|
|
1
|
+
import { Firestore, updateDoc, serverTimestamp, doc, Timestamp } from 'firebase/firestore';
|
|
2
|
+
import {
|
|
3
|
+
Appointment,
|
|
4
|
+
BeforeAfterPerZone,
|
|
5
|
+
APPOINTMENTS_COLLECTION,
|
|
6
|
+
} from '../../../types/appointment';
|
|
7
|
+
import { getAppointmentOrThrow } from './zone-management.utils';
|
|
8
|
+
import { MediaResource } from '../../media/media.service';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Updates visibility fields with audit trail
|
|
12
|
+
* Note: We use Timestamp.now() instead of serverTimestamp() because Firestore
|
|
13
|
+
* doesn't support serverTimestamp() inside arrays. The timestamp will be set
|
|
14
|
+
* on the client side, which is acceptable for audit purposes.
|
|
15
|
+
* @param updates Partial updates object
|
|
16
|
+
* @param doctorId ID of the doctor making the change
|
|
17
|
+
*/
|
|
18
|
+
function addVisibilityAudit(
|
|
19
|
+
updates: Partial<BeforeAfterPerZone>,
|
|
20
|
+
doctorId: string,
|
|
21
|
+
): Partial<BeforeAfterPerZone> {
|
|
22
|
+
// Only add audit fields if visibility-related fields are being updated
|
|
23
|
+
if (
|
|
24
|
+
updates.showToPatient !== undefined ||
|
|
25
|
+
updates.beforeNoteVisibleToPatient !== undefined ||
|
|
26
|
+
updates.afterNoteVisibleToPatient !== undefined
|
|
27
|
+
) {
|
|
28
|
+
return {
|
|
29
|
+
...updates,
|
|
30
|
+
visibilityUpdatedAt: Timestamp.now(), // Use client-side timestamp (Firestore limitation)
|
|
31
|
+
visibilityUpdatedBy: doctorId,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return updates;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Updates a specific photo entry in a zone by index
|
|
39
|
+
* Can update before/after photos and their notes
|
|
40
|
+
*
|
|
41
|
+
* @param db Firestore instance
|
|
42
|
+
* @param appointmentId Appointment ID
|
|
43
|
+
* @param zoneId Zone ID
|
|
44
|
+
* @param photoIndex Index of the photo entry to update
|
|
45
|
+
* @param updates Partial updates to apply
|
|
46
|
+
* @param doctorId Optional doctor ID for audit trail (required if updating visibility)
|
|
47
|
+
* @returns Updated appointment
|
|
48
|
+
*/
|
|
49
|
+
export async function updateZonePhotoEntryUtil(
|
|
50
|
+
db: Firestore,
|
|
51
|
+
appointmentId: string,
|
|
52
|
+
zoneId: string,
|
|
53
|
+
photoIndex: number,
|
|
54
|
+
updates: Partial<BeforeAfterPerZone>,
|
|
55
|
+
doctorId?: string,
|
|
56
|
+
): Promise<Appointment> {
|
|
57
|
+
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
58
|
+
|
|
59
|
+
const zonePhotos = appointment.metadata?.zonePhotos as
|
|
60
|
+
| Record<string, BeforeAfterPerZone[]>
|
|
61
|
+
| undefined
|
|
62
|
+
| null;
|
|
63
|
+
|
|
64
|
+
if (!zonePhotos || !zonePhotos[zoneId] || !Array.isArray(zonePhotos[zoneId])) {
|
|
65
|
+
throw new Error(`No photos found for zone ${zoneId} in appointment ${appointmentId}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const zoneArray = zonePhotos[zoneId];
|
|
69
|
+
if (photoIndex < 0 || photoIndex >= zoneArray.length) {
|
|
70
|
+
throw new Error(`Invalid photo index ${photoIndex} for zone ${zoneId}. Must be between 0 and ${zoneArray.length - 1}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Update the entry with audit trail if visibility is being changed
|
|
74
|
+
const updatesWithAudit = doctorId
|
|
75
|
+
? addVisibilityAudit(updates, doctorId)
|
|
76
|
+
: updates;
|
|
77
|
+
|
|
78
|
+
const updatedZonePhotos = { ...zonePhotos };
|
|
79
|
+
updatedZonePhotos[zoneId] = [...zoneArray];
|
|
80
|
+
updatedZonePhotos[zoneId][photoIndex] = {
|
|
81
|
+
...zoneArray[photoIndex],
|
|
82
|
+
...updatesWithAudit,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Update appointment
|
|
86
|
+
const appointmentRef = doc(db, APPOINTMENTS_COLLECTION, appointmentId);
|
|
87
|
+
await updateDoc(appointmentRef, {
|
|
88
|
+
'metadata.zonePhotos': updatedZonePhotos,
|
|
89
|
+
updatedAt: serverTimestamp(),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return getAppointmentOrThrow(db, appointmentId);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Adds an after photo to an existing before photo entry
|
|
97
|
+
*
|
|
98
|
+
* @param db Firestore instance
|
|
99
|
+
* @param appointmentId Appointment ID
|
|
100
|
+
* @param zoneId Zone ID
|
|
101
|
+
* @param photoIndex Index of the entry to add after photo to
|
|
102
|
+
* @param afterPhotoUrl URL of the after photo
|
|
103
|
+
* @param afterNote Optional note for the after photo
|
|
104
|
+
* @returns Updated appointment
|
|
105
|
+
*/
|
|
106
|
+
export async function addAfterPhotoToEntryUtil(
|
|
107
|
+
db: Firestore,
|
|
108
|
+
appointmentId: string,
|
|
109
|
+
zoneId: string,
|
|
110
|
+
photoIndex: number,
|
|
111
|
+
afterPhotoUrl: MediaResource,
|
|
112
|
+
afterNote?: string
|
|
113
|
+
): Promise<Appointment> {
|
|
114
|
+
return updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, {
|
|
115
|
+
after: afterPhotoUrl,
|
|
116
|
+
afterNote: afterNote || null,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Updates notes for a photo entry
|
|
122
|
+
*
|
|
123
|
+
* @param db Firestore instance
|
|
124
|
+
* @param appointmentId Appointment ID
|
|
125
|
+
* @param zoneId Zone ID
|
|
126
|
+
* @param photoIndex Index of the entry
|
|
127
|
+
* @param beforeNote Optional note for before photo
|
|
128
|
+
* @param afterNote Optional note for after photo
|
|
129
|
+
* @returns Updated appointment
|
|
130
|
+
*/
|
|
131
|
+
export async function updateZonePhotoNotesUtil(
|
|
132
|
+
db: Firestore,
|
|
133
|
+
appointmentId: string,
|
|
134
|
+
zoneId: string,
|
|
135
|
+
photoIndex: number,
|
|
136
|
+
beforeNote?: string,
|
|
137
|
+
afterNote?: string
|
|
138
|
+
): Promise<Appointment> {
|
|
139
|
+
const updates: Partial<BeforeAfterPerZone> = {};
|
|
140
|
+
|
|
141
|
+
if (beforeNote !== undefined) {
|
|
142
|
+
updates.beforeNote = beforeNote || null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (afterNote !== undefined) {
|
|
146
|
+
updates.afterNote = afterNote || null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, updates);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Removes an after photo from an existing entry (keeps the before photo)
|
|
154
|
+
*
|
|
155
|
+
* @param db Firestore instance
|
|
156
|
+
* @param appointmentId Appointment ID
|
|
157
|
+
* @param zoneId Zone ID
|
|
158
|
+
* @param photoIndex Index of the entry to remove after photo from
|
|
159
|
+
* @returns Updated appointment
|
|
160
|
+
*/
|
|
161
|
+
export async function removeAfterPhotoFromEntryUtil(
|
|
162
|
+
db: Firestore,
|
|
163
|
+
appointmentId: string,
|
|
164
|
+
zoneId: string,
|
|
165
|
+
photoIndex: number
|
|
166
|
+
): Promise<Appointment> {
|
|
167
|
+
return updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, {
|
|
168
|
+
after: null,
|
|
169
|
+
afterNote: null,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Gets a specific photo entry from a zone
|
|
175
|
+
*
|
|
176
|
+
* @param db Firestore instance
|
|
177
|
+
* @param appointmentId Appointment ID
|
|
178
|
+
* @param zoneId Zone ID
|
|
179
|
+
* @param photoIndex Index of the entry
|
|
180
|
+
* @returns Photo entry
|
|
181
|
+
*/
|
|
182
|
+
export async function getZonePhotoEntryUtil(
|
|
183
|
+
db: Firestore,
|
|
184
|
+
appointmentId: string,
|
|
185
|
+
zoneId: string,
|
|
186
|
+
photoIndex: number
|
|
187
|
+
): Promise<BeforeAfterPerZone> {
|
|
188
|
+
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
189
|
+
|
|
190
|
+
const zonePhotos = appointment.metadata?.zonePhotos as
|
|
191
|
+
| Record<string, BeforeAfterPerZone[]>
|
|
192
|
+
| undefined
|
|
193
|
+
| null;
|
|
194
|
+
|
|
195
|
+
if (!zonePhotos || !zonePhotos[zoneId] || !Array.isArray(zonePhotos[zoneId])) {
|
|
196
|
+
throw new Error(`No photos found for zone ${zoneId} in appointment ${appointmentId}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const zoneArray = zonePhotos[zoneId];
|
|
200
|
+
if (photoIndex < 0 || photoIndex >= zoneArray.length) {
|
|
201
|
+
throw new Error(`Invalid photo index ${photoIndex} for zone ${zoneId}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return zoneArray[photoIndex];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Updates visibility of a photo pair (before AND after together)
|
|
209
|
+
* Note: If photo pair visibility is disabled, note visibility is automatically disabled as well
|
|
210
|
+
*
|
|
211
|
+
* @param db Firestore instance
|
|
212
|
+
* @param appointmentId Appointment ID
|
|
213
|
+
* @param zoneId Zone ID
|
|
214
|
+
* @param photoIndex Index of the photo entry
|
|
215
|
+
* @param showToPatient Whether the photo pair should be visible to patient
|
|
216
|
+
* @param doctorId ID of the doctor making the change (for audit trail)
|
|
217
|
+
* @returns Updated appointment
|
|
218
|
+
*/
|
|
219
|
+
export async function updateZonePhotoVisibilityUtil(
|
|
220
|
+
db: Firestore,
|
|
221
|
+
appointmentId: string,
|
|
222
|
+
zoneId: string,
|
|
223
|
+
photoIndex: number,
|
|
224
|
+
showToPatient: boolean,
|
|
225
|
+
doctorId: string,
|
|
226
|
+
): Promise<Appointment> {
|
|
227
|
+
const updates: Partial<BeforeAfterPerZone> = { showToPatient };
|
|
228
|
+
|
|
229
|
+
// If hiding photo pair from patient, also hide notes (notes can't be visible if photos aren't)
|
|
230
|
+
if (!showToPatient) {
|
|
231
|
+
updates.beforeNoteVisibleToPatient = false;
|
|
232
|
+
updates.afterNoteVisibleToPatient = false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return updateZonePhotoEntryUtil(
|
|
236
|
+
db,
|
|
237
|
+
appointmentId,
|
|
238
|
+
zoneId,
|
|
239
|
+
photoIndex,
|
|
240
|
+
updates,
|
|
241
|
+
doctorId,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Updates visibility of a photo note (before or after)
|
|
247
|
+
* Note: Notes can only be visible to patient if the photo pair itself is visible to patient
|
|
248
|
+
*
|
|
249
|
+
* @param db Firestore instance
|
|
250
|
+
* @param appointmentId Appointment ID
|
|
251
|
+
* @param zoneId Zone ID
|
|
252
|
+
* @param photoIndex Index of the photo entry
|
|
253
|
+
* @param noteType Type of note ('before' or 'after')
|
|
254
|
+
* @param visibleToPatient Whether the note should be visible to patient
|
|
255
|
+
* @param doctorId ID of the doctor making the change (for audit trail)
|
|
256
|
+
* @returns Updated appointment
|
|
257
|
+
* @throws Error if trying to make note visible when photo pair is not visible
|
|
258
|
+
*/
|
|
259
|
+
export async function updateZonePhotoNoteVisibilityUtil(
|
|
260
|
+
db: Firestore,
|
|
261
|
+
appointmentId: string,
|
|
262
|
+
zoneId: string,
|
|
263
|
+
photoIndex: number,
|
|
264
|
+
noteType: 'before' | 'after',
|
|
265
|
+
visibleToPatient: boolean,
|
|
266
|
+
doctorId: string,
|
|
267
|
+
): Promise<Appointment> {
|
|
268
|
+
// Get current appointment to check if photo pair is visible
|
|
269
|
+
const appointment = await getAppointmentOrThrow(db, appointmentId);
|
|
270
|
+
|
|
271
|
+
const zonePhotos = appointment.metadata?.zonePhotos as
|
|
272
|
+
| Record<string, BeforeAfterPerZone[]>
|
|
273
|
+
| undefined
|
|
274
|
+
| null;
|
|
275
|
+
|
|
276
|
+
if (!zonePhotos || !zonePhotos[zoneId] || !Array.isArray(zonePhotos[zoneId])) {
|
|
277
|
+
throw new Error(`No photos found for zone ${zoneId} in appointment ${appointmentId}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const zoneArray = zonePhotos[zoneId];
|
|
281
|
+
if (photoIndex < 0 || photoIndex >= zoneArray.length) {
|
|
282
|
+
throw new Error(`Invalid photo index ${photoIndex} for zone ${zoneId}. Must be between 0 and ${zoneArray.length - 1}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const photoEntry = zoneArray[photoIndex];
|
|
286
|
+
|
|
287
|
+
// Validate: Notes can only be visible if photo pair is visible
|
|
288
|
+
if (visibleToPatient && !photoEntry.showToPatient) {
|
|
289
|
+
throw new Error(
|
|
290
|
+
'Cannot make note visible to patient: The photo pair must be visible to the patient first. ' +
|
|
291
|
+
'Please enable "Show to Patient" for the photo pair before making notes visible.'
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const updates: Partial<BeforeAfterPerZone> =
|
|
296
|
+
noteType === 'before'
|
|
297
|
+
? { beforeNoteVisibleToPatient: visibleToPatient }
|
|
298
|
+
: { afterNoteVisibleToPatient: visibleToPatient };
|
|
299
|
+
|
|
300
|
+
return updateZonePhotoEntryUtil(db, appointmentId, zoneId, photoIndex, updates, doctorId);
|
|
301
|
+
}
|
|
302
|
+
|