@blackcode_sa/metaestetics-api 1.12.65 → 1.12.67
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 +2 -0
- package/dist/admin/index.d.ts +2 -0
- package/dist/admin/index.js +45 -4
- package/dist/admin/index.mjs +45 -4
- package/dist/backoffice/index.d.mts +33 -0
- package/dist/backoffice/index.d.ts +33 -0
- package/dist/backoffice/index.js +63 -0
- package/dist/backoffice/index.mjs +63 -0
- package/dist/index.d.mts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +116 -11
- package/dist/index.mjs +116 -11
- package/package.json +119 -119
- package/src/__mocks__/firstore.ts +10 -10
- package/src/admin/aggregation/README.md +79 -79
- package/src/admin/aggregation/appointment/README.md +128 -128
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +1844 -1844
- 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 +703 -703
- 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 -641
- package/src/admin/booking/README.md +125 -125
- package/src/admin/booking/booking.admin.ts +1037 -1037
- package/src/admin/booking/booking.calculator.ts +712 -712
- package/src/admin/booking/booking.types.ts +59 -59
- package/src/admin/booking/index.ts +3 -3
- package/src/admin/booking/timezones-problem.md +185 -185
- package/src/admin/calendar/README.md +7 -7
- package/src/admin/calendar/calendar.admin.service.ts +345 -345
- package/src/admin/calendar/index.ts +1 -1
- 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 +75 -75
- package/src/admin/logger/index.ts +78 -78
- package/src/admin/mailing/README.md +95 -95
- package/src/admin/mailing/appointment/appointment.mailing.service.ts +732 -732
- 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/index.ts +3 -3
- 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 +710 -710
- 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 +40 -40
- package/src/backoffice/services/brand.service.ts +256 -256
- package/src/backoffice/services/category.service.ts +341 -318
- 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 +553 -553
- package/src/backoffice/services/requirement.service.ts +235 -235
- package/src/backoffice/services/subcategory.service.ts +417 -395
- package/src/backoffice/services/technology.service.ts +1104 -1083
- 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 -62
- 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 +240 -240
- 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 -163
- 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 +9 -9
- package/src/errors/auth.error.ts +6 -6
- package/src/errors/auth.errors.ts +200 -200
- 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 +293 -293
- package/src/services/__tests__/auth.service.test.ts +346 -346
- package/src/services/__tests__/base.service.test.ts +77 -77
- package/src/services/__tests__/user.service.test.ts +528 -528
- package/src/services/appointment/README.md +17 -17
- package/src/services/appointment/appointment.service.ts +2505 -2505
- package/src/services/appointment/index.ts +1 -1
- package/src/services/appointment/utils/appointment.utils.ts +552 -552
- package/src/services/appointment/utils/extended-procedure.utils.ts +314 -314
- package/src/services/appointment/utils/form-initialization.utils.ts +225 -225
- package/src/services/appointment/utils/recommended-procedure.utils.ts +195 -195
- package/src/services/appointment/utils/zone-management.utils.ts +353 -353
- package/src/services/appointment/utils/zone-photo.utils.ts +152 -152
- package/src/services/auth/auth.service.ts +989 -989
- 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 +1683 -1683
- 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 +646 -646
- 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 +287 -287
- package/src/services/clinic/__tests__/clinic-group.service.test.ts +352 -352
- package/src/services/clinic/__tests__/clinic.service.test.ts +354 -354
- 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 +708 -708
- 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 +949 -949
- package/src/services/clinic/utils/filter.utils.d.ts +23 -23
- package/src/services/clinic/utils/filter.utils.ts +446 -446
- package/src/services/clinic/utils/index.ts +11 -11
- package/src/services/clinic/utils/photos.utils.ts +188 -188
- package/src/services/clinic/utils/search.utils.ts +84 -84
- 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 +587 -587
- package/src/services/documentation-templates/index.ts +2 -2
- package/src/services/index.ts +13 -13
- 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 +294 -294
- package/src/services/patient/index.ts +2 -2
- package/src/services/patient/patient.service.ts +883 -883
- package/src/services/patient/patientRequirements.service.ts +285 -285
- package/src/services/patient/utils/aesthetic-analysis.utils.ts +176 -176
- package/src/services/patient/utils/clinic.utils.ts +80 -80
- package/src/services/patient/utils/docs.utils.ts +142 -142
- 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/profile.utils.ts +510 -510
- package/src/services/patient/utils/sensitive.utils.ts +260 -260
- 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 +1742 -1742
- package/src/services/procedure/README.md +163 -163
- package/src/services/procedure/index.ts +1 -1
- package/src/services/procedure/procedure.service.ts +1715 -1715
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +683 -636
- package/src/services/user/index.ts +1 -1
- package/src/services/user/user.service.ts +489 -489
- package/src/services/user/user.v2.service.ts +466 -466
- package/src/types/appointment/index.ts +480 -480
- package/src/types/calendar/index.ts +258 -258
- package/src/types/calendar/synced-calendar.types.ts +66 -66
- package/src/types/clinic/index.ts +489 -489
- package/src/types/clinic/practitioner-invite.types.ts +91 -91
- package/src/types/clinic/preferences.types.ts +159 -159
- package/src/types/clinic/to-do +3 -3
- package/src/types/documentation-templates/index.ts +308 -308
- package/src/types/index.ts +44 -44
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +265 -265
- package/src/types/patient/aesthetic-analysis.types.ts +66 -66
- package/src/types/patient/allergies.ts +58 -58
- package/src/types/patient/index.ts +275 -275
- package/src/types/patient/medical-info.types.ts +152 -152
- package/src/types/patient/patient-requirements.ts +92 -92
- package/src/types/patient/token.types.ts +61 -61
- package/src/types/practitioner/index.ts +206 -206
- package/src/types/procedure/index.ts +181 -181
- package/src/types/profile/index.ts +39 -39
- package/src/types/reviews/index.ts +132 -130
- package/src/types/tz-lookup.d.ts +4 -4
- package/src/types/user/index.ts +38 -38
- package/src/utils/TIMESTAMPS.md +176 -176
- package/src/utils/TimestampUtils.ts +241 -241
- package/src/utils/index.ts +1 -1
- package/src/validations/appointment.schema.ts +574 -574
- package/src/validations/calendar.schema.ts +225 -225
- package/src/validations/clinic.schema.ts +493 -493
- 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 +20 -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/medical-info.schema.ts +125 -125
- package/src/validations/patient/patient-requirements.schema.ts +84 -84
- package/src/validations/patient/token.schema.ts +29 -29
- package/src/validations/patient.schema.ts +217 -217
- package/src/validations/practitioner.schema.ts +222 -222
- package/src/validations/procedure-product.schema.ts +41 -41
- package/src/validations/procedure.schema.ts +124 -124
- package/src/validations/profile-info.schema.ts +41 -41
- package/src/validations/reviews.schema.ts +195 -189
- package/src/validations/schemas.ts +104 -104
- package/src/validations/shared.schema.ts +78 -78
|
@@ -1,176 +1,176 @@
|
|
|
1
|
-
import { getDoc, updateDoc, setDoc, serverTimestamp, Firestore, doc } from 'firebase/firestore';
|
|
2
|
-
import {
|
|
3
|
-
AestheticAnalysis,
|
|
4
|
-
CreateAestheticAnalysisData,
|
|
5
|
-
UpdateAestheticAnalysisData,
|
|
6
|
-
AESTHETIC_ANALYSIS_COLLECTION,
|
|
7
|
-
PATIENTS_COLLECTION,
|
|
8
|
-
AestheticAnalysisStatus,
|
|
9
|
-
} from '../../../types/patient';
|
|
10
|
-
import { UserRole } from '../../../types';
|
|
11
|
-
import {
|
|
12
|
-
createAestheticAnalysisSchema,
|
|
13
|
-
updateAestheticAnalysisSchema,
|
|
14
|
-
aestheticAnalysisSchema,
|
|
15
|
-
} from '../../../validations/patient/aesthetic-analysis.schema';
|
|
16
|
-
import { getPatientDocRef } from './docs.utils';
|
|
17
|
-
import { AuthError } from '../../../errors/auth.errors';
|
|
18
|
-
import { getPractitionerProfileByUserRef } from './practitioner.utils';
|
|
19
|
-
import { getClinicAdminByUserRef } from '../../clinic/utils/admin.utils';
|
|
20
|
-
|
|
21
|
-
export const getAestheticAnalysisDocRef = (db: Firestore, patientId: string) => {
|
|
22
|
-
return doc(db, PATIENTS_COLLECTION, patientId, AESTHETIC_ANALYSIS_COLLECTION, patientId);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const checkAestheticAnalysisAccessUtil = async (
|
|
26
|
-
db: Firestore,
|
|
27
|
-
patientId: string,
|
|
28
|
-
requesterId: string,
|
|
29
|
-
requesterRoles: UserRole[]
|
|
30
|
-
): Promise<void> => {
|
|
31
|
-
const patientDoc = await getDoc(getPatientDocRef(db, patientId));
|
|
32
|
-
if (!patientDoc.exists()) {
|
|
33
|
-
throw new Error('Patient profile not found');
|
|
34
|
-
}
|
|
35
|
-
const patientData = patientDoc.data() as any;
|
|
36
|
-
|
|
37
|
-
if (patientData.userRef && patientData.userRef === requesterId) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (requesterRoles.includes(UserRole.PRACTITIONER)) {
|
|
42
|
-
const practitionerProfile = await getPractitionerProfileByUserRef(db, requesterId);
|
|
43
|
-
if (practitionerProfile && patientData.doctorIds?.includes(practitionerProfile.id)) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (requesterRoles.includes(UserRole.CLINIC_ADMIN)) {
|
|
49
|
-
const adminProfile = await getClinicAdminByUserRef(db, requesterId);
|
|
50
|
-
if (adminProfile && adminProfile.clinicsManaged) {
|
|
51
|
-
const hasAccess = adminProfile.clinicsManaged.some((managedClinicId) =>
|
|
52
|
-
patientData.clinicIds?.includes(managedClinicId)
|
|
53
|
-
);
|
|
54
|
-
if (hasAccess) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
throw new AuthError(
|
|
61
|
-
'Unauthorized access to aesthetic analysis.',
|
|
62
|
-
'AUTH/UNAUTHORIZED_ACCESS',
|
|
63
|
-
403
|
|
64
|
-
);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
export const calculateCompletionPercentage = (data: Partial<AestheticAnalysis>): number => {
|
|
68
|
-
let completed = 0;
|
|
69
|
-
const total = 4;
|
|
70
|
-
|
|
71
|
-
if (data.selectedConcerns && data.selectedConcerns.length > 0) completed++;
|
|
72
|
-
|
|
73
|
-
if (data.patientGoals && (
|
|
74
|
-
data.patientGoals.timeline ||
|
|
75
|
-
data.patientGoals.budget ||
|
|
76
|
-
data.patientGoals.selectedTemplate
|
|
77
|
-
)) completed++;
|
|
78
|
-
|
|
79
|
-
if (data.assessmentScales && Object.keys(data.assessmentScales).length > 0) completed++;
|
|
80
|
-
|
|
81
|
-
if (data.clinicalFindings && Object.keys(data.clinicalFindings).length > 0) completed++;
|
|
82
|
-
|
|
83
|
-
return Math.round((completed / total) * 100);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export const determineStatus = (completionPercentage: number, selectedConcerns: string[]): AestheticAnalysisStatus => {
|
|
87
|
-
if (completionPercentage < 50) {
|
|
88
|
-
return 'incomplete';
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (completionPercentage >= 50 && selectedConcerns.length > 0) {
|
|
92
|
-
return 'ready_for_planning';
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return 'incomplete';
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
export const getAestheticAnalysisUtil = async (
|
|
99
|
-
db: Firestore,
|
|
100
|
-
patientId: string,
|
|
101
|
-
requesterId: string,
|
|
102
|
-
requesterRoles: UserRole[]
|
|
103
|
-
): Promise<AestheticAnalysis | null> => {
|
|
104
|
-
await checkAestheticAnalysisAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
105
|
-
|
|
106
|
-
const docRef = getAestheticAnalysisDocRef(db, patientId);
|
|
107
|
-
const snapshot = await getDoc(docRef);
|
|
108
|
-
|
|
109
|
-
if (!snapshot.exists()) {
|
|
110
|
-
return null;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const data = snapshot.data();
|
|
114
|
-
return aestheticAnalysisSchema.parse({
|
|
115
|
-
...data,
|
|
116
|
-
id: patientId,
|
|
117
|
-
});
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
export const createOrUpdateAestheticAnalysisUtil = async (
|
|
121
|
-
db: Firestore,
|
|
122
|
-
patientId: string,
|
|
123
|
-
data: CreateAestheticAnalysisData | UpdateAestheticAnalysisData,
|
|
124
|
-
requesterId: string,
|
|
125
|
-
requesterRoles: UserRole[],
|
|
126
|
-
isUpdate: boolean = false
|
|
127
|
-
): Promise<void> => {
|
|
128
|
-
await checkAestheticAnalysisAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
129
|
-
|
|
130
|
-
const validatedData = isUpdate
|
|
131
|
-
? updateAestheticAnalysisSchema.parse(data)
|
|
132
|
-
: createAestheticAnalysisSchema.parse(data);
|
|
133
|
-
|
|
134
|
-
const docRef = getAestheticAnalysisDocRef(db, patientId);
|
|
135
|
-
const snapshot = await getDoc(docRef);
|
|
136
|
-
|
|
137
|
-
const requesterRole = requesterRoles.includes(UserRole.PRACTITIONER) ? 'PRACTITIONER' : 'PATIENT';
|
|
138
|
-
|
|
139
|
-
const existingData = snapshot.exists() ? snapshot.data() : null;
|
|
140
|
-
const mergedData: any = {
|
|
141
|
-
...(existingData || {}),
|
|
142
|
-
...validatedData,
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const completionPercentage = calculateCompletionPercentage(mergedData);
|
|
146
|
-
const status = determineStatus(
|
|
147
|
-
completionPercentage,
|
|
148
|
-
mergedData.selectedConcerns || []
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
if (!snapshot.exists()) {
|
|
152
|
-
await setDoc(docRef, {
|
|
153
|
-
id: patientId,
|
|
154
|
-
patientId,
|
|
155
|
-
selectedConcerns: [],
|
|
156
|
-
clinicalFindings: {},
|
|
157
|
-
assessmentScales: {},
|
|
158
|
-
...validatedData,
|
|
159
|
-
completionPercentage,
|
|
160
|
-
status,
|
|
161
|
-
lastUpdatedBy: requesterId,
|
|
162
|
-
lastUpdatedByRole: requesterRole,
|
|
163
|
-
createdAt: serverTimestamp(),
|
|
164
|
-
updatedAt: serverTimestamp(),
|
|
165
|
-
});
|
|
166
|
-
} else {
|
|
167
|
-
await updateDoc(docRef, {
|
|
168
|
-
...validatedData,
|
|
169
|
-
completionPercentage,
|
|
170
|
-
status,
|
|
171
|
-
lastUpdatedBy: requesterId,
|
|
172
|
-
lastUpdatedByRole: requesterRole,
|
|
173
|
-
updatedAt: serverTimestamp(),
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
};
|
|
1
|
+
import { getDoc, updateDoc, setDoc, serverTimestamp, Firestore, doc } from 'firebase/firestore';
|
|
2
|
+
import {
|
|
3
|
+
AestheticAnalysis,
|
|
4
|
+
CreateAestheticAnalysisData,
|
|
5
|
+
UpdateAestheticAnalysisData,
|
|
6
|
+
AESTHETIC_ANALYSIS_COLLECTION,
|
|
7
|
+
PATIENTS_COLLECTION,
|
|
8
|
+
AestheticAnalysisStatus,
|
|
9
|
+
} from '../../../types/patient';
|
|
10
|
+
import { UserRole } from '../../../types';
|
|
11
|
+
import {
|
|
12
|
+
createAestheticAnalysisSchema,
|
|
13
|
+
updateAestheticAnalysisSchema,
|
|
14
|
+
aestheticAnalysisSchema,
|
|
15
|
+
} from '../../../validations/patient/aesthetic-analysis.schema';
|
|
16
|
+
import { getPatientDocRef } from './docs.utils';
|
|
17
|
+
import { AuthError } from '../../../errors/auth.errors';
|
|
18
|
+
import { getPractitionerProfileByUserRef } from './practitioner.utils';
|
|
19
|
+
import { getClinicAdminByUserRef } from '../../clinic/utils/admin.utils';
|
|
20
|
+
|
|
21
|
+
export const getAestheticAnalysisDocRef = (db: Firestore, patientId: string) => {
|
|
22
|
+
return doc(db, PATIENTS_COLLECTION, patientId, AESTHETIC_ANALYSIS_COLLECTION, patientId);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const checkAestheticAnalysisAccessUtil = async (
|
|
26
|
+
db: Firestore,
|
|
27
|
+
patientId: string,
|
|
28
|
+
requesterId: string,
|
|
29
|
+
requesterRoles: UserRole[]
|
|
30
|
+
): Promise<void> => {
|
|
31
|
+
const patientDoc = await getDoc(getPatientDocRef(db, patientId));
|
|
32
|
+
if (!patientDoc.exists()) {
|
|
33
|
+
throw new Error('Patient profile not found');
|
|
34
|
+
}
|
|
35
|
+
const patientData = patientDoc.data() as any;
|
|
36
|
+
|
|
37
|
+
if (patientData.userRef && patientData.userRef === requesterId) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (requesterRoles.includes(UserRole.PRACTITIONER)) {
|
|
42
|
+
const practitionerProfile = await getPractitionerProfileByUserRef(db, requesterId);
|
|
43
|
+
if (practitionerProfile && patientData.doctorIds?.includes(practitionerProfile.id)) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (requesterRoles.includes(UserRole.CLINIC_ADMIN)) {
|
|
49
|
+
const adminProfile = await getClinicAdminByUserRef(db, requesterId);
|
|
50
|
+
if (adminProfile && adminProfile.clinicsManaged) {
|
|
51
|
+
const hasAccess = adminProfile.clinicsManaged.some((managedClinicId) =>
|
|
52
|
+
patientData.clinicIds?.includes(managedClinicId)
|
|
53
|
+
);
|
|
54
|
+
if (hasAccess) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw new AuthError(
|
|
61
|
+
'Unauthorized access to aesthetic analysis.',
|
|
62
|
+
'AUTH/UNAUTHORIZED_ACCESS',
|
|
63
|
+
403
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const calculateCompletionPercentage = (data: Partial<AestheticAnalysis>): number => {
|
|
68
|
+
let completed = 0;
|
|
69
|
+
const total = 4;
|
|
70
|
+
|
|
71
|
+
if (data.selectedConcerns && data.selectedConcerns.length > 0) completed++;
|
|
72
|
+
|
|
73
|
+
if (data.patientGoals && (
|
|
74
|
+
data.patientGoals.timeline ||
|
|
75
|
+
data.patientGoals.budget ||
|
|
76
|
+
data.patientGoals.selectedTemplate
|
|
77
|
+
)) completed++;
|
|
78
|
+
|
|
79
|
+
if (data.assessmentScales && Object.keys(data.assessmentScales).length > 0) completed++;
|
|
80
|
+
|
|
81
|
+
if (data.clinicalFindings && Object.keys(data.clinicalFindings).length > 0) completed++;
|
|
82
|
+
|
|
83
|
+
return Math.round((completed / total) * 100);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const determineStatus = (completionPercentage: number, selectedConcerns: string[]): AestheticAnalysisStatus => {
|
|
87
|
+
if (completionPercentage < 50) {
|
|
88
|
+
return 'incomplete';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (completionPercentage >= 50 && selectedConcerns.length > 0) {
|
|
92
|
+
return 'ready_for_planning';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return 'incomplete';
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const getAestheticAnalysisUtil = async (
|
|
99
|
+
db: Firestore,
|
|
100
|
+
patientId: string,
|
|
101
|
+
requesterId: string,
|
|
102
|
+
requesterRoles: UserRole[]
|
|
103
|
+
): Promise<AestheticAnalysis | null> => {
|
|
104
|
+
await checkAestheticAnalysisAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
105
|
+
|
|
106
|
+
const docRef = getAestheticAnalysisDocRef(db, patientId);
|
|
107
|
+
const snapshot = await getDoc(docRef);
|
|
108
|
+
|
|
109
|
+
if (!snapshot.exists()) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const data = snapshot.data();
|
|
114
|
+
return aestheticAnalysisSchema.parse({
|
|
115
|
+
...data,
|
|
116
|
+
id: patientId,
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export const createOrUpdateAestheticAnalysisUtil = async (
|
|
121
|
+
db: Firestore,
|
|
122
|
+
patientId: string,
|
|
123
|
+
data: CreateAestheticAnalysisData | UpdateAestheticAnalysisData,
|
|
124
|
+
requesterId: string,
|
|
125
|
+
requesterRoles: UserRole[],
|
|
126
|
+
isUpdate: boolean = false
|
|
127
|
+
): Promise<void> => {
|
|
128
|
+
await checkAestheticAnalysisAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
129
|
+
|
|
130
|
+
const validatedData = isUpdate
|
|
131
|
+
? updateAestheticAnalysisSchema.parse(data)
|
|
132
|
+
: createAestheticAnalysisSchema.parse(data);
|
|
133
|
+
|
|
134
|
+
const docRef = getAestheticAnalysisDocRef(db, patientId);
|
|
135
|
+
const snapshot = await getDoc(docRef);
|
|
136
|
+
|
|
137
|
+
const requesterRole = requesterRoles.includes(UserRole.PRACTITIONER) ? 'PRACTITIONER' : 'PATIENT';
|
|
138
|
+
|
|
139
|
+
const existingData = snapshot.exists() ? snapshot.data() : null;
|
|
140
|
+
const mergedData: any = {
|
|
141
|
+
...(existingData || {}),
|
|
142
|
+
...validatedData,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const completionPercentage = calculateCompletionPercentage(mergedData);
|
|
146
|
+
const status = determineStatus(
|
|
147
|
+
completionPercentage,
|
|
148
|
+
mergedData.selectedConcerns || []
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (!snapshot.exists()) {
|
|
152
|
+
await setDoc(docRef, {
|
|
153
|
+
id: patientId,
|
|
154
|
+
patientId,
|
|
155
|
+
selectedConcerns: [],
|
|
156
|
+
clinicalFindings: {},
|
|
157
|
+
assessmentScales: {},
|
|
158
|
+
...validatedData,
|
|
159
|
+
completionPercentage,
|
|
160
|
+
status,
|
|
161
|
+
lastUpdatedBy: requesterId,
|
|
162
|
+
lastUpdatedByRole: requesterRole,
|
|
163
|
+
createdAt: serverTimestamp(),
|
|
164
|
+
updatedAt: serverTimestamp(),
|
|
165
|
+
});
|
|
166
|
+
} else {
|
|
167
|
+
await updateDoc(docRef, {
|
|
168
|
+
...validatedData,
|
|
169
|
+
completionPercentage,
|
|
170
|
+
status,
|
|
171
|
+
lastUpdatedBy: requesterId,
|
|
172
|
+
lastUpdatedByRole: requesterRole,
|
|
173
|
+
updatedAt: serverTimestamp(),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
};
|
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collection,
|
|
3
|
-
query,
|
|
4
|
-
where,
|
|
5
|
-
getDocs,
|
|
6
|
-
Firestore,
|
|
7
|
-
limit,
|
|
8
|
-
startAfter,
|
|
9
|
-
doc,
|
|
10
|
-
getDoc,
|
|
11
|
-
QueryConstraint,
|
|
12
|
-
} from "firebase/firestore";
|
|
13
|
-
import { PatientProfile, PATIENTS_COLLECTION } from "../../../types/patient";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Retrieves all patients associated with a specific clinic with pagination support.
|
|
17
|
-
*
|
|
18
|
-
* @param {Firestore} db - Firestore instance
|
|
19
|
-
* @param {string} clinicId - ID of the clinic whose patients to retrieve
|
|
20
|
-
* @param {Object} options - Optional parameters for pagination
|
|
21
|
-
* @param {number} options.limit - Maximum number of profiles to return
|
|
22
|
-
* @param {string} options.startAfter - The ID of the document to start after (for pagination)
|
|
23
|
-
* @returns {Promise<PatientProfile[]>} A promise resolving to an array of patient profiles
|
|
24
|
-
*/
|
|
25
|
-
export const getPatientsByClinicUtil = async (
|
|
26
|
-
db: Firestore,
|
|
27
|
-
clinicId: string,
|
|
28
|
-
options?: { limit?: number; startAfter?: string }
|
|
29
|
-
): Promise<PatientProfile[]> => {
|
|
30
|
-
try {
|
|
31
|
-
console.log(
|
|
32
|
-
`[getPatientsByClinicUtil] Fetching patients for clinic ID: ${clinicId} with options:`,
|
|
33
|
-
options
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const patientsCollection = collection(db, PATIENTS_COLLECTION);
|
|
37
|
-
const constraints: QueryConstraint[] = [
|
|
38
|
-
where("clinicIds", "array-contains", clinicId),
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
let q = query(patientsCollection, ...constraints);
|
|
42
|
-
|
|
43
|
-
// Apply pagination if needed
|
|
44
|
-
if (options?.limit) {
|
|
45
|
-
q = query(q, limit(options.limit));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// If startAfter is provided, get that document and use it for pagination
|
|
49
|
-
if (options?.startAfter) {
|
|
50
|
-
const startAfterDoc = await getDoc(
|
|
51
|
-
doc(db, PATIENTS_COLLECTION, options.startAfter)
|
|
52
|
-
);
|
|
53
|
-
if (startAfterDoc.exists()) {
|
|
54
|
-
q = query(q, startAfter(startAfterDoc));
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const patientsSnapshot = await getDocs(q);
|
|
59
|
-
|
|
60
|
-
const patients: PatientProfile[] = [];
|
|
61
|
-
patientsSnapshot.forEach((doc) => {
|
|
62
|
-
patients.push(doc.data() as PatientProfile);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
console.log(
|
|
66
|
-
`[getPatientsByClinicUtil] Found ${patients.length} patients for clinic ID: ${clinicId}`
|
|
67
|
-
);
|
|
68
|
-
return patients;
|
|
69
|
-
} catch (error) {
|
|
70
|
-
console.error(
|
|
71
|
-
`[getPatientsByClinicUtil] Error fetching patients for clinic:`,
|
|
72
|
-
error
|
|
73
|
-
);
|
|
74
|
-
throw new Error(
|
|
75
|
-
`Failed to retrieve patients for clinic: ${
|
|
76
|
-
error instanceof Error ? error.message : String(error)
|
|
77
|
-
}`
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
collection,
|
|
3
|
+
query,
|
|
4
|
+
where,
|
|
5
|
+
getDocs,
|
|
6
|
+
Firestore,
|
|
7
|
+
limit,
|
|
8
|
+
startAfter,
|
|
9
|
+
doc,
|
|
10
|
+
getDoc,
|
|
11
|
+
QueryConstraint,
|
|
12
|
+
} from "firebase/firestore";
|
|
13
|
+
import { PatientProfile, PATIENTS_COLLECTION } from "../../../types/patient";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Retrieves all patients associated with a specific clinic with pagination support.
|
|
17
|
+
*
|
|
18
|
+
* @param {Firestore} db - Firestore instance
|
|
19
|
+
* @param {string} clinicId - ID of the clinic whose patients to retrieve
|
|
20
|
+
* @param {Object} options - Optional parameters for pagination
|
|
21
|
+
* @param {number} options.limit - Maximum number of profiles to return
|
|
22
|
+
* @param {string} options.startAfter - The ID of the document to start after (for pagination)
|
|
23
|
+
* @returns {Promise<PatientProfile[]>} A promise resolving to an array of patient profiles
|
|
24
|
+
*/
|
|
25
|
+
export const getPatientsByClinicUtil = async (
|
|
26
|
+
db: Firestore,
|
|
27
|
+
clinicId: string,
|
|
28
|
+
options?: { limit?: number; startAfter?: string }
|
|
29
|
+
): Promise<PatientProfile[]> => {
|
|
30
|
+
try {
|
|
31
|
+
console.log(
|
|
32
|
+
`[getPatientsByClinicUtil] Fetching patients for clinic ID: ${clinicId} with options:`,
|
|
33
|
+
options
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const patientsCollection = collection(db, PATIENTS_COLLECTION);
|
|
37
|
+
const constraints: QueryConstraint[] = [
|
|
38
|
+
where("clinicIds", "array-contains", clinicId),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
let q = query(patientsCollection, ...constraints);
|
|
42
|
+
|
|
43
|
+
// Apply pagination if needed
|
|
44
|
+
if (options?.limit) {
|
|
45
|
+
q = query(q, limit(options.limit));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// If startAfter is provided, get that document and use it for pagination
|
|
49
|
+
if (options?.startAfter) {
|
|
50
|
+
const startAfterDoc = await getDoc(
|
|
51
|
+
doc(db, PATIENTS_COLLECTION, options.startAfter)
|
|
52
|
+
);
|
|
53
|
+
if (startAfterDoc.exists()) {
|
|
54
|
+
q = query(q, startAfter(startAfterDoc));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const patientsSnapshot = await getDocs(q);
|
|
59
|
+
|
|
60
|
+
const patients: PatientProfile[] = [];
|
|
61
|
+
patientsSnapshot.forEach((doc) => {
|
|
62
|
+
patients.push(doc.data() as PatientProfile);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
console.log(
|
|
66
|
+
`[getPatientsByClinicUtil] Found ${patients.length} patients for clinic ID: ${clinicId}`
|
|
67
|
+
);
|
|
68
|
+
return patients;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(
|
|
71
|
+
`[getPatientsByClinicUtil] Error fetching patients for clinic:`,
|
|
72
|
+
error
|
|
73
|
+
);
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Failed to retrieve patients for clinic: ${
|
|
76
|
+
error instanceof Error ? error.message : String(error)
|
|
77
|
+
}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
};
|