@blackcode_sa/metaestetics-api 1.13.5 → 1.13.6
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 +20 -1
- package/dist/admin/index.d.ts +20 -1
- package/dist/admin/index.js +217 -1
- package/dist/admin/index.mjs +217 -1
- package/package.json +121 -121
- 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 +1984 -1984
- 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 -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 -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 +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 +81 -81
- 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 +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 +256 -256
- 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 +553 -553
- 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 +1151 -1151
- 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 +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 -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 +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/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 +2142 -2142
- 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 +2558 -2558
- 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 +14 -14
- 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 +2200 -2200
- package/src/services/reviews/index.ts +1 -1
- package/src/services/reviews/reviews.service.ts +734 -734
- 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/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 +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 +498 -498
- 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 +47 -47
- package/src/types/notifications/README.md +77 -77
- package/src/types/notifications/index.ts +286 -286
- 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 -132
- 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 +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 +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 -195
- package/src/validations/schemas.ts +104 -104
- package/src/validations/shared.schema.ts +78 -78
|
@@ -1,458 +1,458 @@
|
|
|
1
|
-
import {
|
|
2
|
-
doc,
|
|
3
|
-
getDoc,
|
|
4
|
-
updateDoc,
|
|
5
|
-
setDoc,
|
|
6
|
-
serverTimestamp,
|
|
7
|
-
arrayUnion,
|
|
8
|
-
arrayRemove,
|
|
9
|
-
Firestore,
|
|
10
|
-
Timestamp,
|
|
11
|
-
} from "firebase/firestore";
|
|
12
|
-
import {
|
|
13
|
-
PatientMedicalInfo,
|
|
14
|
-
CreatePatientMedicalInfoData,
|
|
15
|
-
UpdateVitalStatsData,
|
|
16
|
-
AddAllergyData,
|
|
17
|
-
UpdateAllergyData,
|
|
18
|
-
AddBlockingConditionData,
|
|
19
|
-
UpdateBlockingConditionData,
|
|
20
|
-
AddContraindicationData,
|
|
21
|
-
UpdateContraindicationData,
|
|
22
|
-
AddMedicationData,
|
|
23
|
-
UpdateMedicationData,
|
|
24
|
-
DEFAULT_MEDICAL_INFO,
|
|
25
|
-
PATIENTS_COLLECTION,
|
|
26
|
-
PATIENT_MEDICAL_INFO_COLLECTION,
|
|
27
|
-
PatientDoctor,
|
|
28
|
-
type PatientProfile,
|
|
29
|
-
} from "../../../types/patient";
|
|
30
|
-
import {
|
|
31
|
-
createPatientMedicalInfoSchema,
|
|
32
|
-
updateVitalStatsSchema,
|
|
33
|
-
addAllergySchema,
|
|
34
|
-
updateAllergySchema,
|
|
35
|
-
addBlockingConditionSchema,
|
|
36
|
-
updateBlockingConditionSchema,
|
|
37
|
-
addContraindicationSchema,
|
|
38
|
-
updateContraindicationSchema,
|
|
39
|
-
addMedicationSchema,
|
|
40
|
-
updateMedicationSchema,
|
|
41
|
-
patientMedicalInfoSchema,
|
|
42
|
-
} from "../../../validations/patient/medical-info.schema";
|
|
43
|
-
import { z } from "zod";
|
|
44
|
-
import { AuthError } from "../../../errors/auth.errors";
|
|
45
|
-
import { UserRole } from "../../../types";
|
|
46
|
-
import { getMedicalInfoDocRef, getPatientDocRef } from "./docs.utils";
|
|
47
|
-
import { getPractitionerProfileByUserRef } from "./practitioner.utils";
|
|
48
|
-
import { getClinicAdminByUserRef } from "../../clinic/utils/admin.utils";
|
|
49
|
-
|
|
50
|
-
// Pomoćna funkcija za proveru i inicijalizaciju medical info dokumenta
|
|
51
|
-
export const ensureMedicalInfoExists = async (
|
|
52
|
-
db: Firestore,
|
|
53
|
-
patientId: string,
|
|
54
|
-
requesterId: string
|
|
55
|
-
): Promise<void> => {
|
|
56
|
-
const medicalInfoRef = getMedicalInfoDocRef(db, patientId);
|
|
57
|
-
const medicalInfoDoc = await getDoc(medicalInfoRef);
|
|
58
|
-
|
|
59
|
-
if (!medicalInfoDoc.exists()) {
|
|
60
|
-
const defaultData = {
|
|
61
|
-
...DEFAULT_MEDICAL_INFO,
|
|
62
|
-
patientId,
|
|
63
|
-
lastUpdated: serverTimestamp(),
|
|
64
|
-
updatedBy: requesterId, // Koristimo ID onoga ko zahteva akciju
|
|
65
|
-
};
|
|
66
|
-
await setDoc(medicalInfoRef, defaultData);
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Pomoćna funkcija za proveru pristupa
|
|
71
|
-
const checkMedicalAccessUtil = async (
|
|
72
|
-
db: Firestore,
|
|
73
|
-
patientId: string,
|
|
74
|
-
requesterId: string,
|
|
75
|
-
requesterRoles: UserRole[]
|
|
76
|
-
): Promise<void> => {
|
|
77
|
-
const patientDoc = await getDoc(getPatientDocRef(db, patientId));
|
|
78
|
-
if (!patientDoc.exists()) {
|
|
79
|
-
throw new Error("Patient profile not found");
|
|
80
|
-
}
|
|
81
|
-
const patientData = patientDoc.data() as PatientProfile;
|
|
82
|
-
|
|
83
|
-
// 1. Provera da li je pacijent vlasnik profila (ako profil nije manuelan)
|
|
84
|
-
if (patientData.userRef && patientData.userRef === requesterId) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 2. Provera za doktora
|
|
89
|
-
if (requesterRoles.includes(UserRole.PRACTITIONER)) {
|
|
90
|
-
const practitionerProfile = await getPractitionerProfileByUserRef(
|
|
91
|
-
db,
|
|
92
|
-
requesterId
|
|
93
|
-
);
|
|
94
|
-
if (
|
|
95
|
-
practitionerProfile &&
|
|
96
|
-
patientData.doctorIds?.includes(practitionerProfile.id)
|
|
97
|
-
) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// 3. Provera za admina klinike
|
|
103
|
-
if (requesterRoles.includes(UserRole.CLINIC_ADMIN)) {
|
|
104
|
-
const adminProfile = await getClinicAdminByUserRef(db, requesterId);
|
|
105
|
-
if (adminProfile && adminProfile.clinicsManaged) {
|
|
106
|
-
// Check if any of the admin's managed clinics are in the patient's clinic list
|
|
107
|
-
const hasAccess = adminProfile.clinicsManaged.some((managedClinicId) =>
|
|
108
|
-
patientData.clinicIds?.includes(managedClinicId)
|
|
109
|
-
);
|
|
110
|
-
if (hasAccess) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Ako nijedan uslov nije zadovoljen
|
|
117
|
-
throw new AuthError(
|
|
118
|
-
"Unauthorized access to medical information.",
|
|
119
|
-
"AUTH/UNAUTHORIZED_ACCESS",
|
|
120
|
-
403
|
|
121
|
-
);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// Osnovne metode za medicinske informacije
|
|
125
|
-
export const createMedicalInfoUtil = async (
|
|
126
|
-
db: Firestore,
|
|
127
|
-
patientId: string,
|
|
128
|
-
data: CreatePatientMedicalInfoData,
|
|
129
|
-
requesterId: string,
|
|
130
|
-
requesterRoles: UserRole[]
|
|
131
|
-
): Promise<void> => {
|
|
132
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
133
|
-
const validatedData = createPatientMedicalInfoSchema.parse(data);
|
|
134
|
-
|
|
135
|
-
await setDoc(getMedicalInfoDocRef(db, patientId), {
|
|
136
|
-
...validatedData,
|
|
137
|
-
patientId: patientId,
|
|
138
|
-
lastUpdated: serverTimestamp(),
|
|
139
|
-
updatedBy: requesterId,
|
|
140
|
-
});
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
export const getMedicalInfoUtil = async (
|
|
144
|
-
db: Firestore,
|
|
145
|
-
patientId: string,
|
|
146
|
-
requesterId: string,
|
|
147
|
-
requesterRoles: UserRole[]
|
|
148
|
-
): Promise<PatientMedicalInfo> => {
|
|
149
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
150
|
-
const docRef = getMedicalInfoDocRef(db, patientId);
|
|
151
|
-
const snapshot = await getDoc(docRef);
|
|
152
|
-
if (!snapshot.exists()) {
|
|
153
|
-
// Ako ne postoji, kreiraj prazan dokument
|
|
154
|
-
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
155
|
-
const newSnapshot = await getDoc(docRef);
|
|
156
|
-
return patientMedicalInfoSchema.parse(newSnapshot.data());
|
|
157
|
-
}
|
|
158
|
-
return patientMedicalInfoSchema.parse(snapshot.data());
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// Funkcije za rad sa vitalnim statistikama
|
|
162
|
-
export const updateVitalStatsUtil = async (
|
|
163
|
-
db: Firestore,
|
|
164
|
-
patientId: string,
|
|
165
|
-
data: UpdateVitalStatsData,
|
|
166
|
-
requesterId: string,
|
|
167
|
-
requesterRoles: UserRole[] // Dodajemo role za proveru pristupa
|
|
168
|
-
): Promise<void> => {
|
|
169
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
170
|
-
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
171
|
-
const validatedData = updateVitalStatsSchema.parse(data);
|
|
172
|
-
|
|
173
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
174
|
-
vitalStats: validatedData,
|
|
175
|
-
lastUpdated: serverTimestamp(),
|
|
176
|
-
updatedBy: requesterId,
|
|
177
|
-
});
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
// Funkcije za rad sa alergijama
|
|
181
|
-
export const addAllergyUtil = async (
|
|
182
|
-
db: Firestore,
|
|
183
|
-
patientId: string,
|
|
184
|
-
data: AddAllergyData,
|
|
185
|
-
requesterId: string,
|
|
186
|
-
requesterRoles: UserRole[]
|
|
187
|
-
): Promise<void> => {
|
|
188
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
189
|
-
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
190
|
-
const validatedData = addAllergySchema.parse(data);
|
|
191
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
192
|
-
allergies: arrayUnion(validatedData),
|
|
193
|
-
lastUpdated: serverTimestamp(),
|
|
194
|
-
updatedBy: requesterId,
|
|
195
|
-
});
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
export const updateAllergyUtil = async (
|
|
199
|
-
db: Firestore,
|
|
200
|
-
patientId: string,
|
|
201
|
-
data: UpdateAllergyData,
|
|
202
|
-
requesterId: string,
|
|
203
|
-
requesterRoles: UserRole[]
|
|
204
|
-
): Promise<void> => {
|
|
205
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
206
|
-
const validatedData = updateAllergySchema.parse(data);
|
|
207
|
-
const { allergyIndex, ...updateData } = validatedData;
|
|
208
|
-
const docSnapshot = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
209
|
-
if (!docSnapshot.exists()) throw new Error("Medical info not found");
|
|
210
|
-
const medicalInfo = patientMedicalInfoSchema.parse(docSnapshot.data());
|
|
211
|
-
if (allergyIndex >= medicalInfo.allergies.length) {
|
|
212
|
-
throw new Error("Invalid allergy index");
|
|
213
|
-
}
|
|
214
|
-
const updatedAllergies = [...medicalInfo.allergies];
|
|
215
|
-
updatedAllergies[allergyIndex] = {
|
|
216
|
-
...updatedAllergies[allergyIndex],
|
|
217
|
-
...updateData,
|
|
218
|
-
};
|
|
219
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
220
|
-
allergies: updatedAllergies,
|
|
221
|
-
lastUpdated: serverTimestamp(),
|
|
222
|
-
updatedBy: requesterId,
|
|
223
|
-
});
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
export const removeAllergyUtil = async (
|
|
227
|
-
db: Firestore,
|
|
228
|
-
patientId: string,
|
|
229
|
-
allergyIndex: number,
|
|
230
|
-
requesterId: string,
|
|
231
|
-
requesterRoles: UserRole[]
|
|
232
|
-
): Promise<void> => {
|
|
233
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
234
|
-
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
235
|
-
if (!doc.exists()) throw new Error("Medical info not found");
|
|
236
|
-
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
237
|
-
if (allergyIndex >= medicalInfo.allergies.length) {
|
|
238
|
-
throw new Error("Invalid allergy index");
|
|
239
|
-
}
|
|
240
|
-
const updatedAllergies = medicalInfo.allergies.filter(
|
|
241
|
-
(_, index) => index !== allergyIndex
|
|
242
|
-
);
|
|
243
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
244
|
-
allergies: updatedAllergies,
|
|
245
|
-
lastUpdated: serverTimestamp(),
|
|
246
|
-
updatedBy: requesterId,
|
|
247
|
-
});
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
// Funkcije za rad sa blocking conditions
|
|
251
|
-
export const addBlockingConditionUtil = async (
|
|
252
|
-
db: Firestore,
|
|
253
|
-
patientId: string,
|
|
254
|
-
data: AddBlockingConditionData,
|
|
255
|
-
requesterId: string,
|
|
256
|
-
requesterRoles: UserRole[]
|
|
257
|
-
): Promise<void> => {
|
|
258
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
259
|
-
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
260
|
-
const validatedData = addBlockingConditionSchema.parse(data);
|
|
261
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
262
|
-
blockingConditions: arrayUnion(validatedData),
|
|
263
|
-
lastUpdated: serverTimestamp(),
|
|
264
|
-
updatedBy: requesterId,
|
|
265
|
-
});
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
export const updateBlockingConditionUtil = async (
|
|
269
|
-
db: Firestore,
|
|
270
|
-
patientId: string,
|
|
271
|
-
data: UpdateBlockingConditionData,
|
|
272
|
-
requesterId: string,
|
|
273
|
-
requesterRoles: UserRole[]
|
|
274
|
-
): Promise<void> => {
|
|
275
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
276
|
-
const validatedData = updateBlockingConditionSchema.parse(data);
|
|
277
|
-
const { conditionIndex, ...updateData } = validatedData;
|
|
278
|
-
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
279
|
-
if (!doc.exists()) throw new Error("Medical info not found");
|
|
280
|
-
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
281
|
-
if (conditionIndex >= medicalInfo.blockingConditions.length) {
|
|
282
|
-
throw new Error("Invalid blocking condition index");
|
|
283
|
-
}
|
|
284
|
-
const updatedConditions = [...medicalInfo.blockingConditions];
|
|
285
|
-
updatedConditions[conditionIndex] = {
|
|
286
|
-
...updatedConditions[conditionIndex],
|
|
287
|
-
...updateData,
|
|
288
|
-
};
|
|
289
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
290
|
-
blockingConditions: updatedConditions,
|
|
291
|
-
lastUpdated: serverTimestamp(),
|
|
292
|
-
updatedBy: requesterId,
|
|
293
|
-
});
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
export const removeBlockingConditionUtil = async (
|
|
297
|
-
db: Firestore,
|
|
298
|
-
patientId: string,
|
|
299
|
-
conditionIndex: number,
|
|
300
|
-
requesterId: string,
|
|
301
|
-
requesterRoles: UserRole[]
|
|
302
|
-
): Promise<void> => {
|
|
303
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
304
|
-
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
305
|
-
if (!doc.exists()) throw new Error("Medical info not found");
|
|
306
|
-
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
307
|
-
if (conditionIndex >= medicalInfo.blockingConditions.length) {
|
|
308
|
-
throw new Error("Invalid blocking condition index");
|
|
309
|
-
}
|
|
310
|
-
const updatedConditions = medicalInfo.blockingConditions.filter(
|
|
311
|
-
(_, index) => index !== conditionIndex
|
|
312
|
-
);
|
|
313
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
314
|
-
blockingConditions: updatedConditions,
|
|
315
|
-
lastUpdated: serverTimestamp(),
|
|
316
|
-
updatedBy: requesterId,
|
|
317
|
-
});
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
// Funkcije za rad sa kontraindikacijama
|
|
321
|
-
export const addContraindicationUtil = async (
|
|
322
|
-
db: Firestore,
|
|
323
|
-
patientId: string,
|
|
324
|
-
data: AddContraindicationData,
|
|
325
|
-
requesterId: string,
|
|
326
|
-
requesterRoles: UserRole[]
|
|
327
|
-
): Promise<void> => {
|
|
328
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
329
|
-
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
330
|
-
const validatedData = addContraindicationSchema.parse(data);
|
|
331
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
332
|
-
contraindications: arrayUnion(validatedData),
|
|
333
|
-
lastUpdated: serverTimestamp(),
|
|
334
|
-
updatedBy: requesterId,
|
|
335
|
-
});
|
|
336
|
-
};
|
|
337
|
-
|
|
338
|
-
export const updateContraindicationUtil = async (
|
|
339
|
-
db: Firestore,
|
|
340
|
-
patientId: string,
|
|
341
|
-
data: UpdateContraindicationData,
|
|
342
|
-
requesterId: string,
|
|
343
|
-
requesterRoles: UserRole[]
|
|
344
|
-
): Promise<void> => {
|
|
345
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
346
|
-
const validatedData = updateContraindicationSchema.parse(data);
|
|
347
|
-
const { contraindicationIndex, ...updateData } = validatedData;
|
|
348
|
-
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
349
|
-
if (!doc.exists()) throw new Error("Medical info not found");
|
|
350
|
-
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
351
|
-
if (contraindicationIndex >= medicalInfo.contraindications.length) {
|
|
352
|
-
throw new Error("Invalid contraindication index");
|
|
353
|
-
}
|
|
354
|
-
const updatedContraindications = [...medicalInfo.contraindications];
|
|
355
|
-
updatedContraindications[contraindicationIndex] = {
|
|
356
|
-
...updatedContraindications[contraindicationIndex],
|
|
357
|
-
...updateData,
|
|
358
|
-
};
|
|
359
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
360
|
-
contraindications: updatedContraindications,
|
|
361
|
-
lastUpdated: serverTimestamp(),
|
|
362
|
-
updatedBy: requesterId,
|
|
363
|
-
});
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
export const removeContraindicationUtil = async (
|
|
367
|
-
db: Firestore,
|
|
368
|
-
patientId: string,
|
|
369
|
-
contraindicationIndex: number,
|
|
370
|
-
requesterId: string,
|
|
371
|
-
requesterRoles: UserRole[]
|
|
372
|
-
): Promise<void> => {
|
|
373
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
374
|
-
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
375
|
-
if (!doc.exists()) throw new Error("Medical info not found");
|
|
376
|
-
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
377
|
-
if (contraindicationIndex >= medicalInfo.contraindications.length) {
|
|
378
|
-
throw new Error("Invalid contraindication index");
|
|
379
|
-
}
|
|
380
|
-
const updatedContraindications = medicalInfo.contraindications.filter(
|
|
381
|
-
(_, index) => index !== contraindicationIndex
|
|
382
|
-
);
|
|
383
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
384
|
-
contraindications: updatedContraindications,
|
|
385
|
-
lastUpdated: serverTimestamp(),
|
|
386
|
-
updatedBy: requesterId,
|
|
387
|
-
});
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
// Funkcije za rad sa medikacijama
|
|
391
|
-
export const addMedicationUtil = async (
|
|
392
|
-
db: Firestore,
|
|
393
|
-
patientId: string,
|
|
394
|
-
data: AddMedicationData,
|
|
395
|
-
requesterId: string,
|
|
396
|
-
requesterRoles: UserRole[]
|
|
397
|
-
): Promise<void> => {
|
|
398
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
399
|
-
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
400
|
-
const validatedData = addMedicationSchema.parse(data);
|
|
401
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
402
|
-
currentMedications: arrayUnion(validatedData),
|
|
403
|
-
lastUpdated: serverTimestamp(),
|
|
404
|
-
updatedBy: requesterId,
|
|
405
|
-
});
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
export const updateMedicationUtil = async (
|
|
409
|
-
db: Firestore,
|
|
410
|
-
patientId: string,
|
|
411
|
-
data: UpdateMedicationData,
|
|
412
|
-
requesterId: string,
|
|
413
|
-
requesterRoles: UserRole[]
|
|
414
|
-
): Promise<void> => {
|
|
415
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
416
|
-
const validatedData = updateMedicationSchema.parse(data);
|
|
417
|
-
const { medicationIndex, ...updateData } = validatedData;
|
|
418
|
-
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
419
|
-
if (!doc.exists()) throw new Error("Medical info not found");
|
|
420
|
-
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
421
|
-
if (medicationIndex >= medicalInfo.currentMedications.length) {
|
|
422
|
-
throw new Error("Invalid medication index");
|
|
423
|
-
}
|
|
424
|
-
const updatedMedications = [...medicalInfo.currentMedications];
|
|
425
|
-
updatedMedications[medicationIndex] = {
|
|
426
|
-
...updatedMedications[medicationIndex],
|
|
427
|
-
...updateData,
|
|
428
|
-
};
|
|
429
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
430
|
-
currentMedications: updatedMedications,
|
|
431
|
-
lastUpdated: serverTimestamp(),
|
|
432
|
-
updatedBy: requesterId,
|
|
433
|
-
});
|
|
434
|
-
};
|
|
435
|
-
|
|
436
|
-
export const removeMedicationUtil = async (
|
|
437
|
-
db: Firestore,
|
|
438
|
-
patientId: string,
|
|
439
|
-
medicationIndex: number,
|
|
440
|
-
requesterId: string,
|
|
441
|
-
requesterRoles: UserRole[]
|
|
442
|
-
): Promise<void> => {
|
|
443
|
-
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
444
|
-
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
445
|
-
if (!doc.exists()) throw new Error("Medical info not found");
|
|
446
|
-
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
447
|
-
if (medicationIndex >= medicalInfo.currentMedications.length) {
|
|
448
|
-
throw new Error("Invalid medication index");
|
|
449
|
-
}
|
|
450
|
-
const updatedMedications = medicalInfo.currentMedications.filter(
|
|
451
|
-
(_, index) => index !== medicationIndex
|
|
452
|
-
);
|
|
453
|
-
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
454
|
-
currentMedications: updatedMedications,
|
|
455
|
-
lastUpdated: serverTimestamp(),
|
|
456
|
-
updatedBy: requesterId,
|
|
457
|
-
});
|
|
458
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
doc,
|
|
3
|
+
getDoc,
|
|
4
|
+
updateDoc,
|
|
5
|
+
setDoc,
|
|
6
|
+
serverTimestamp,
|
|
7
|
+
arrayUnion,
|
|
8
|
+
arrayRemove,
|
|
9
|
+
Firestore,
|
|
10
|
+
Timestamp,
|
|
11
|
+
} from "firebase/firestore";
|
|
12
|
+
import {
|
|
13
|
+
PatientMedicalInfo,
|
|
14
|
+
CreatePatientMedicalInfoData,
|
|
15
|
+
UpdateVitalStatsData,
|
|
16
|
+
AddAllergyData,
|
|
17
|
+
UpdateAllergyData,
|
|
18
|
+
AddBlockingConditionData,
|
|
19
|
+
UpdateBlockingConditionData,
|
|
20
|
+
AddContraindicationData,
|
|
21
|
+
UpdateContraindicationData,
|
|
22
|
+
AddMedicationData,
|
|
23
|
+
UpdateMedicationData,
|
|
24
|
+
DEFAULT_MEDICAL_INFO,
|
|
25
|
+
PATIENTS_COLLECTION,
|
|
26
|
+
PATIENT_MEDICAL_INFO_COLLECTION,
|
|
27
|
+
PatientDoctor,
|
|
28
|
+
type PatientProfile,
|
|
29
|
+
} from "../../../types/patient";
|
|
30
|
+
import {
|
|
31
|
+
createPatientMedicalInfoSchema,
|
|
32
|
+
updateVitalStatsSchema,
|
|
33
|
+
addAllergySchema,
|
|
34
|
+
updateAllergySchema,
|
|
35
|
+
addBlockingConditionSchema,
|
|
36
|
+
updateBlockingConditionSchema,
|
|
37
|
+
addContraindicationSchema,
|
|
38
|
+
updateContraindicationSchema,
|
|
39
|
+
addMedicationSchema,
|
|
40
|
+
updateMedicationSchema,
|
|
41
|
+
patientMedicalInfoSchema,
|
|
42
|
+
} from "../../../validations/patient/medical-info.schema";
|
|
43
|
+
import { z } from "zod";
|
|
44
|
+
import { AuthError } from "../../../errors/auth.errors";
|
|
45
|
+
import { UserRole } from "../../../types";
|
|
46
|
+
import { getMedicalInfoDocRef, getPatientDocRef } from "./docs.utils";
|
|
47
|
+
import { getPractitionerProfileByUserRef } from "./practitioner.utils";
|
|
48
|
+
import { getClinicAdminByUserRef } from "../../clinic/utils/admin.utils";
|
|
49
|
+
|
|
50
|
+
// Pomoćna funkcija za proveru i inicijalizaciju medical info dokumenta
|
|
51
|
+
export const ensureMedicalInfoExists = async (
|
|
52
|
+
db: Firestore,
|
|
53
|
+
patientId: string,
|
|
54
|
+
requesterId: string
|
|
55
|
+
): Promise<void> => {
|
|
56
|
+
const medicalInfoRef = getMedicalInfoDocRef(db, patientId);
|
|
57
|
+
const medicalInfoDoc = await getDoc(medicalInfoRef);
|
|
58
|
+
|
|
59
|
+
if (!medicalInfoDoc.exists()) {
|
|
60
|
+
const defaultData = {
|
|
61
|
+
...DEFAULT_MEDICAL_INFO,
|
|
62
|
+
patientId,
|
|
63
|
+
lastUpdated: serverTimestamp(),
|
|
64
|
+
updatedBy: requesterId, // Koristimo ID onoga ko zahteva akciju
|
|
65
|
+
};
|
|
66
|
+
await setDoc(medicalInfoRef, defaultData);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Pomoćna funkcija za proveru pristupa
|
|
71
|
+
const checkMedicalAccessUtil = async (
|
|
72
|
+
db: Firestore,
|
|
73
|
+
patientId: string,
|
|
74
|
+
requesterId: string,
|
|
75
|
+
requesterRoles: UserRole[]
|
|
76
|
+
): Promise<void> => {
|
|
77
|
+
const patientDoc = await getDoc(getPatientDocRef(db, patientId));
|
|
78
|
+
if (!patientDoc.exists()) {
|
|
79
|
+
throw new Error("Patient profile not found");
|
|
80
|
+
}
|
|
81
|
+
const patientData = patientDoc.data() as PatientProfile;
|
|
82
|
+
|
|
83
|
+
// 1. Provera da li je pacijent vlasnik profila (ako profil nije manuelan)
|
|
84
|
+
if (patientData.userRef && patientData.userRef === requesterId) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 2. Provera za doktora
|
|
89
|
+
if (requesterRoles.includes(UserRole.PRACTITIONER)) {
|
|
90
|
+
const practitionerProfile = await getPractitionerProfileByUserRef(
|
|
91
|
+
db,
|
|
92
|
+
requesterId
|
|
93
|
+
);
|
|
94
|
+
if (
|
|
95
|
+
practitionerProfile &&
|
|
96
|
+
patientData.doctorIds?.includes(practitionerProfile.id)
|
|
97
|
+
) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 3. Provera za admina klinike
|
|
103
|
+
if (requesterRoles.includes(UserRole.CLINIC_ADMIN)) {
|
|
104
|
+
const adminProfile = await getClinicAdminByUserRef(db, requesterId);
|
|
105
|
+
if (adminProfile && adminProfile.clinicsManaged) {
|
|
106
|
+
// Check if any of the admin's managed clinics are in the patient's clinic list
|
|
107
|
+
const hasAccess = adminProfile.clinicsManaged.some((managedClinicId) =>
|
|
108
|
+
patientData.clinicIds?.includes(managedClinicId)
|
|
109
|
+
);
|
|
110
|
+
if (hasAccess) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Ako nijedan uslov nije zadovoljen
|
|
117
|
+
throw new AuthError(
|
|
118
|
+
"Unauthorized access to medical information.",
|
|
119
|
+
"AUTH/UNAUTHORIZED_ACCESS",
|
|
120
|
+
403
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Osnovne metode za medicinske informacije
|
|
125
|
+
export const createMedicalInfoUtil = async (
|
|
126
|
+
db: Firestore,
|
|
127
|
+
patientId: string,
|
|
128
|
+
data: CreatePatientMedicalInfoData,
|
|
129
|
+
requesterId: string,
|
|
130
|
+
requesterRoles: UserRole[]
|
|
131
|
+
): Promise<void> => {
|
|
132
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
133
|
+
const validatedData = createPatientMedicalInfoSchema.parse(data);
|
|
134
|
+
|
|
135
|
+
await setDoc(getMedicalInfoDocRef(db, patientId), {
|
|
136
|
+
...validatedData,
|
|
137
|
+
patientId: patientId,
|
|
138
|
+
lastUpdated: serverTimestamp(),
|
|
139
|
+
updatedBy: requesterId,
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const getMedicalInfoUtil = async (
|
|
144
|
+
db: Firestore,
|
|
145
|
+
patientId: string,
|
|
146
|
+
requesterId: string,
|
|
147
|
+
requesterRoles: UserRole[]
|
|
148
|
+
): Promise<PatientMedicalInfo> => {
|
|
149
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
150
|
+
const docRef = getMedicalInfoDocRef(db, patientId);
|
|
151
|
+
const snapshot = await getDoc(docRef);
|
|
152
|
+
if (!snapshot.exists()) {
|
|
153
|
+
// Ako ne postoji, kreiraj prazan dokument
|
|
154
|
+
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
155
|
+
const newSnapshot = await getDoc(docRef);
|
|
156
|
+
return patientMedicalInfoSchema.parse(newSnapshot.data());
|
|
157
|
+
}
|
|
158
|
+
return patientMedicalInfoSchema.parse(snapshot.data());
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Funkcije za rad sa vitalnim statistikama
|
|
162
|
+
export const updateVitalStatsUtil = async (
|
|
163
|
+
db: Firestore,
|
|
164
|
+
patientId: string,
|
|
165
|
+
data: UpdateVitalStatsData,
|
|
166
|
+
requesterId: string,
|
|
167
|
+
requesterRoles: UserRole[] // Dodajemo role za proveru pristupa
|
|
168
|
+
): Promise<void> => {
|
|
169
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
170
|
+
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
171
|
+
const validatedData = updateVitalStatsSchema.parse(data);
|
|
172
|
+
|
|
173
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
174
|
+
vitalStats: validatedData,
|
|
175
|
+
lastUpdated: serverTimestamp(),
|
|
176
|
+
updatedBy: requesterId,
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Funkcije za rad sa alergijama
|
|
181
|
+
export const addAllergyUtil = async (
|
|
182
|
+
db: Firestore,
|
|
183
|
+
patientId: string,
|
|
184
|
+
data: AddAllergyData,
|
|
185
|
+
requesterId: string,
|
|
186
|
+
requesterRoles: UserRole[]
|
|
187
|
+
): Promise<void> => {
|
|
188
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
189
|
+
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
190
|
+
const validatedData = addAllergySchema.parse(data);
|
|
191
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
192
|
+
allergies: arrayUnion(validatedData),
|
|
193
|
+
lastUpdated: serverTimestamp(),
|
|
194
|
+
updatedBy: requesterId,
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export const updateAllergyUtil = async (
|
|
199
|
+
db: Firestore,
|
|
200
|
+
patientId: string,
|
|
201
|
+
data: UpdateAllergyData,
|
|
202
|
+
requesterId: string,
|
|
203
|
+
requesterRoles: UserRole[]
|
|
204
|
+
): Promise<void> => {
|
|
205
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
206
|
+
const validatedData = updateAllergySchema.parse(data);
|
|
207
|
+
const { allergyIndex, ...updateData } = validatedData;
|
|
208
|
+
const docSnapshot = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
209
|
+
if (!docSnapshot.exists()) throw new Error("Medical info not found");
|
|
210
|
+
const medicalInfo = patientMedicalInfoSchema.parse(docSnapshot.data());
|
|
211
|
+
if (allergyIndex >= medicalInfo.allergies.length) {
|
|
212
|
+
throw new Error("Invalid allergy index");
|
|
213
|
+
}
|
|
214
|
+
const updatedAllergies = [...medicalInfo.allergies];
|
|
215
|
+
updatedAllergies[allergyIndex] = {
|
|
216
|
+
...updatedAllergies[allergyIndex],
|
|
217
|
+
...updateData,
|
|
218
|
+
};
|
|
219
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
220
|
+
allergies: updatedAllergies,
|
|
221
|
+
lastUpdated: serverTimestamp(),
|
|
222
|
+
updatedBy: requesterId,
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const removeAllergyUtil = async (
|
|
227
|
+
db: Firestore,
|
|
228
|
+
patientId: string,
|
|
229
|
+
allergyIndex: number,
|
|
230
|
+
requesterId: string,
|
|
231
|
+
requesterRoles: UserRole[]
|
|
232
|
+
): Promise<void> => {
|
|
233
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
234
|
+
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
235
|
+
if (!doc.exists()) throw new Error("Medical info not found");
|
|
236
|
+
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
237
|
+
if (allergyIndex >= medicalInfo.allergies.length) {
|
|
238
|
+
throw new Error("Invalid allergy index");
|
|
239
|
+
}
|
|
240
|
+
const updatedAllergies = medicalInfo.allergies.filter(
|
|
241
|
+
(_, index) => index !== allergyIndex
|
|
242
|
+
);
|
|
243
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
244
|
+
allergies: updatedAllergies,
|
|
245
|
+
lastUpdated: serverTimestamp(),
|
|
246
|
+
updatedBy: requesterId,
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Funkcije za rad sa blocking conditions
|
|
251
|
+
export const addBlockingConditionUtil = async (
|
|
252
|
+
db: Firestore,
|
|
253
|
+
patientId: string,
|
|
254
|
+
data: AddBlockingConditionData,
|
|
255
|
+
requesterId: string,
|
|
256
|
+
requesterRoles: UserRole[]
|
|
257
|
+
): Promise<void> => {
|
|
258
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
259
|
+
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
260
|
+
const validatedData = addBlockingConditionSchema.parse(data);
|
|
261
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
262
|
+
blockingConditions: arrayUnion(validatedData),
|
|
263
|
+
lastUpdated: serverTimestamp(),
|
|
264
|
+
updatedBy: requesterId,
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export const updateBlockingConditionUtil = async (
|
|
269
|
+
db: Firestore,
|
|
270
|
+
patientId: string,
|
|
271
|
+
data: UpdateBlockingConditionData,
|
|
272
|
+
requesterId: string,
|
|
273
|
+
requesterRoles: UserRole[]
|
|
274
|
+
): Promise<void> => {
|
|
275
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
276
|
+
const validatedData = updateBlockingConditionSchema.parse(data);
|
|
277
|
+
const { conditionIndex, ...updateData } = validatedData;
|
|
278
|
+
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
279
|
+
if (!doc.exists()) throw new Error("Medical info not found");
|
|
280
|
+
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
281
|
+
if (conditionIndex >= medicalInfo.blockingConditions.length) {
|
|
282
|
+
throw new Error("Invalid blocking condition index");
|
|
283
|
+
}
|
|
284
|
+
const updatedConditions = [...medicalInfo.blockingConditions];
|
|
285
|
+
updatedConditions[conditionIndex] = {
|
|
286
|
+
...updatedConditions[conditionIndex],
|
|
287
|
+
...updateData,
|
|
288
|
+
};
|
|
289
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
290
|
+
blockingConditions: updatedConditions,
|
|
291
|
+
lastUpdated: serverTimestamp(),
|
|
292
|
+
updatedBy: requesterId,
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export const removeBlockingConditionUtil = async (
|
|
297
|
+
db: Firestore,
|
|
298
|
+
patientId: string,
|
|
299
|
+
conditionIndex: number,
|
|
300
|
+
requesterId: string,
|
|
301
|
+
requesterRoles: UserRole[]
|
|
302
|
+
): Promise<void> => {
|
|
303
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
304
|
+
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
305
|
+
if (!doc.exists()) throw new Error("Medical info not found");
|
|
306
|
+
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
307
|
+
if (conditionIndex >= medicalInfo.blockingConditions.length) {
|
|
308
|
+
throw new Error("Invalid blocking condition index");
|
|
309
|
+
}
|
|
310
|
+
const updatedConditions = medicalInfo.blockingConditions.filter(
|
|
311
|
+
(_, index) => index !== conditionIndex
|
|
312
|
+
);
|
|
313
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
314
|
+
blockingConditions: updatedConditions,
|
|
315
|
+
lastUpdated: serverTimestamp(),
|
|
316
|
+
updatedBy: requesterId,
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// Funkcije za rad sa kontraindikacijama
|
|
321
|
+
export const addContraindicationUtil = async (
|
|
322
|
+
db: Firestore,
|
|
323
|
+
patientId: string,
|
|
324
|
+
data: AddContraindicationData,
|
|
325
|
+
requesterId: string,
|
|
326
|
+
requesterRoles: UserRole[]
|
|
327
|
+
): Promise<void> => {
|
|
328
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
329
|
+
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
330
|
+
const validatedData = addContraindicationSchema.parse(data);
|
|
331
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
332
|
+
contraindications: arrayUnion(validatedData),
|
|
333
|
+
lastUpdated: serverTimestamp(),
|
|
334
|
+
updatedBy: requesterId,
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
export const updateContraindicationUtil = async (
|
|
339
|
+
db: Firestore,
|
|
340
|
+
patientId: string,
|
|
341
|
+
data: UpdateContraindicationData,
|
|
342
|
+
requesterId: string,
|
|
343
|
+
requesterRoles: UserRole[]
|
|
344
|
+
): Promise<void> => {
|
|
345
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
346
|
+
const validatedData = updateContraindicationSchema.parse(data);
|
|
347
|
+
const { contraindicationIndex, ...updateData } = validatedData;
|
|
348
|
+
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
349
|
+
if (!doc.exists()) throw new Error("Medical info not found");
|
|
350
|
+
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
351
|
+
if (contraindicationIndex >= medicalInfo.contraindications.length) {
|
|
352
|
+
throw new Error("Invalid contraindication index");
|
|
353
|
+
}
|
|
354
|
+
const updatedContraindications = [...medicalInfo.contraindications];
|
|
355
|
+
updatedContraindications[contraindicationIndex] = {
|
|
356
|
+
...updatedContraindications[contraindicationIndex],
|
|
357
|
+
...updateData,
|
|
358
|
+
};
|
|
359
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
360
|
+
contraindications: updatedContraindications,
|
|
361
|
+
lastUpdated: serverTimestamp(),
|
|
362
|
+
updatedBy: requesterId,
|
|
363
|
+
});
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
export const removeContraindicationUtil = async (
|
|
367
|
+
db: Firestore,
|
|
368
|
+
patientId: string,
|
|
369
|
+
contraindicationIndex: number,
|
|
370
|
+
requesterId: string,
|
|
371
|
+
requesterRoles: UserRole[]
|
|
372
|
+
): Promise<void> => {
|
|
373
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
374
|
+
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
375
|
+
if (!doc.exists()) throw new Error("Medical info not found");
|
|
376
|
+
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
377
|
+
if (contraindicationIndex >= medicalInfo.contraindications.length) {
|
|
378
|
+
throw new Error("Invalid contraindication index");
|
|
379
|
+
}
|
|
380
|
+
const updatedContraindications = medicalInfo.contraindications.filter(
|
|
381
|
+
(_, index) => index !== contraindicationIndex
|
|
382
|
+
);
|
|
383
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
384
|
+
contraindications: updatedContraindications,
|
|
385
|
+
lastUpdated: serverTimestamp(),
|
|
386
|
+
updatedBy: requesterId,
|
|
387
|
+
});
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Funkcije za rad sa medikacijama
|
|
391
|
+
export const addMedicationUtil = async (
|
|
392
|
+
db: Firestore,
|
|
393
|
+
patientId: string,
|
|
394
|
+
data: AddMedicationData,
|
|
395
|
+
requesterId: string,
|
|
396
|
+
requesterRoles: UserRole[]
|
|
397
|
+
): Promise<void> => {
|
|
398
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
399
|
+
await ensureMedicalInfoExists(db, patientId, requesterId);
|
|
400
|
+
const validatedData = addMedicationSchema.parse(data);
|
|
401
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
402
|
+
currentMedications: arrayUnion(validatedData),
|
|
403
|
+
lastUpdated: serverTimestamp(),
|
|
404
|
+
updatedBy: requesterId,
|
|
405
|
+
});
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
export const updateMedicationUtil = async (
|
|
409
|
+
db: Firestore,
|
|
410
|
+
patientId: string,
|
|
411
|
+
data: UpdateMedicationData,
|
|
412
|
+
requesterId: string,
|
|
413
|
+
requesterRoles: UserRole[]
|
|
414
|
+
): Promise<void> => {
|
|
415
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
416
|
+
const validatedData = updateMedicationSchema.parse(data);
|
|
417
|
+
const { medicationIndex, ...updateData } = validatedData;
|
|
418
|
+
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
419
|
+
if (!doc.exists()) throw new Error("Medical info not found");
|
|
420
|
+
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
421
|
+
if (medicationIndex >= medicalInfo.currentMedications.length) {
|
|
422
|
+
throw new Error("Invalid medication index");
|
|
423
|
+
}
|
|
424
|
+
const updatedMedications = [...medicalInfo.currentMedications];
|
|
425
|
+
updatedMedications[medicationIndex] = {
|
|
426
|
+
...updatedMedications[medicationIndex],
|
|
427
|
+
...updateData,
|
|
428
|
+
};
|
|
429
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
430
|
+
currentMedications: updatedMedications,
|
|
431
|
+
lastUpdated: serverTimestamp(),
|
|
432
|
+
updatedBy: requesterId,
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
export const removeMedicationUtil = async (
|
|
437
|
+
db: Firestore,
|
|
438
|
+
patientId: string,
|
|
439
|
+
medicationIndex: number,
|
|
440
|
+
requesterId: string,
|
|
441
|
+
requesterRoles: UserRole[]
|
|
442
|
+
): Promise<void> => {
|
|
443
|
+
await checkMedicalAccessUtil(db, patientId, requesterId, requesterRoles);
|
|
444
|
+
const doc = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
445
|
+
if (!doc.exists()) throw new Error("Medical info not found");
|
|
446
|
+
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
447
|
+
if (medicationIndex >= medicalInfo.currentMedications.length) {
|
|
448
|
+
throw new Error("Invalid medication index");
|
|
449
|
+
}
|
|
450
|
+
const updatedMedications = medicalInfo.currentMedications.filter(
|
|
451
|
+
(_, index) => index !== medicationIndex
|
|
452
|
+
);
|
|
453
|
+
await updateDoc(getMedicalInfoDocRef(db, patientId), {
|
|
454
|
+
currentMedications: updatedMedications,
|
|
455
|
+
lastUpdated: serverTimestamp(),
|
|
456
|
+
updatedBy: requesterId,
|
|
457
|
+
});
|
|
458
|
+
};
|