@blackcode_sa/metaestetics-api 1.7.1 → 1.7.3
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 +1 -0
- package/dist/admin/index.d.ts +1 -0
- package/dist/admin/index.js +1 -0
- package/dist/admin/index.mjs +1 -0
- package/dist/index.d.mts +280 -341
- package/dist/index.d.ts +280 -341
- package/dist/index.js +1173 -1122
- package/dist/index.mjs +383 -332
- package/package.json +1 -1
- package/src/admin/documentation-templates/document-manager.admin.ts +1 -0
- package/src/services/patient/utils/medical.utils.ts +26 -23
- package/src/services/patient/utils/practitioner.utils.ts +102 -0
- package/src/types/appointment/index.ts +1 -0
- package/src/types/patient/medical-info.types.ts +2 -2
- package/src/validations/common.schema.ts +15 -4
package/package.json
CHANGED
|
@@ -245,6 +245,7 @@ export class DocumentManagerAdminService {
|
|
|
245
245
|
title: template.title,
|
|
246
246
|
isUserForm: filledDocumentData.isUserForm,
|
|
247
247
|
isRequired: filledDocumentData.isRequired,
|
|
248
|
+
sortingOrder: templateRef.sortingOrder,
|
|
248
249
|
status: filledDocumentData.status,
|
|
249
250
|
path: docRef.path,
|
|
250
251
|
};
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
PATIENTS_COLLECTION,
|
|
26
26
|
PATIENT_MEDICAL_INFO_COLLECTION,
|
|
27
27
|
PatientDoctor,
|
|
28
|
+
type PatientProfile,
|
|
28
29
|
} from "../../../types/patient";
|
|
29
30
|
import {
|
|
30
31
|
createPatientMedicalInfoSchema,
|
|
@@ -43,6 +44,7 @@ import { z } from "zod";
|
|
|
43
44
|
import { AuthError } from "../../../errors/auth.errors";
|
|
44
45
|
import { UserRole } from "../../../types";
|
|
45
46
|
import { getMedicalInfoDocRef, getPatientDocRef } from "./docs.utils";
|
|
47
|
+
import { getPractitionerProfileByUserRef } from "./practitioner.utils";
|
|
46
48
|
|
|
47
49
|
// Pomoćna funkcija za proveru i inicijalizaciju medical info dokumenta
|
|
48
50
|
export const ensureMedicalInfoExists = async (
|
|
@@ -117,32 +119,29 @@ const checkMedicalAccessUtil = async (
|
|
|
117
119
|
throw new Error("Patient profile not found");
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
const patientData = patientDoc.data();
|
|
122
|
+
const patientData = patientDoc.data() as PatientProfile;
|
|
121
123
|
|
|
122
124
|
// Proveri da li je korisnik vlasnik profila
|
|
123
125
|
if (patientData.userRef === userRef) return;
|
|
124
126
|
|
|
125
|
-
//
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
131
|
-
) {
|
|
132
|
-
throw new AuthError(
|
|
133
|
-
"Nedozvoljen pristup medicinskim informacijama",
|
|
134
|
-
"AUTH/UNAUTHORIZED_ACCESS",
|
|
135
|
-
403
|
|
127
|
+
// Ako je doktor, proveri da li je povezan sa pacijentom
|
|
128
|
+
if (userRoles.includes(UserRole.PRACTITIONER)) {
|
|
129
|
+
const practitionerProfile = await getPractitionerProfileByUserRef(
|
|
130
|
+
db,
|
|
131
|
+
userRef
|
|
136
132
|
);
|
|
137
|
-
}
|
|
138
133
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
134
|
+
if (!practitionerProfile) {
|
|
135
|
+
throw new AuthError(
|
|
136
|
+
"Practitioner profile not found",
|
|
137
|
+
"AUTH/UNAUTHORIZED_ACCESS",
|
|
138
|
+
403
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Check if practitioner's ID is in the patient's doctorIds array
|
|
143
|
+
const isAssignedDoctor = patientData.doctorIds?.includes(
|
|
144
|
+
practitionerProfile.id
|
|
146
145
|
);
|
|
147
146
|
|
|
148
147
|
if (!isAssignedDoctor) {
|
|
@@ -190,8 +189,10 @@ export const getMedicalInfoUtil = async (
|
|
|
190
189
|
throw new Error("Medicinske informacije nisu pronađene");
|
|
191
190
|
}
|
|
192
191
|
|
|
192
|
+
// The schema will transform raw timestamp objects to Timestamp instances
|
|
193
193
|
return patientMedicalInfoSchema.parse(snapshot.data());
|
|
194
194
|
};
|
|
195
|
+
|
|
195
196
|
// Funkcije za rad sa vitalnim statistikama
|
|
196
197
|
export const updateVitalStatsUtil = async (
|
|
197
198
|
db: Firestore,
|
|
@@ -235,10 +236,12 @@ export const updateAllergyUtil = async (
|
|
|
235
236
|
const validatedData = updateAllergySchema.parse(data);
|
|
236
237
|
const { allergyIndex, ...updateData } = validatedData;
|
|
237
238
|
|
|
238
|
-
const
|
|
239
|
-
if (!
|
|
239
|
+
const docSnapshot = await getDoc(getMedicalInfoDocRef(db, patientId));
|
|
240
|
+
if (!docSnapshot.exists()) throw new Error("Medical info not found");
|
|
241
|
+
|
|
242
|
+
// Parse through schema to ensure proper Timestamp objects
|
|
243
|
+
const medicalInfo = patientMedicalInfoSchema.parse(docSnapshot.data());
|
|
240
244
|
|
|
241
|
-
const medicalInfo = doc.data() as PatientMedicalInfo;
|
|
242
245
|
if (allergyIndex >= medicalInfo.allergies.length) {
|
|
243
246
|
throw new Error("Invalid allergy index");
|
|
244
247
|
}
|
|
@@ -17,6 +17,10 @@ import {
|
|
|
17
17
|
PATIENTS_COLLECTION,
|
|
18
18
|
} from "../../../types/patient";
|
|
19
19
|
import { getSensitiveInfoDocRef } from "./docs.utils";
|
|
20
|
+
import {
|
|
21
|
+
Practitioner,
|
|
22
|
+
PRACTITIONERS_COLLECTION,
|
|
23
|
+
} from "../../../types/practitioner";
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* Retrieves all patients associated with a specific practitioner with pagination support.
|
|
@@ -156,3 +160,101 @@ export const getPatientsByPractitionerWithDetailsUtil = async (
|
|
|
156
160
|
);
|
|
157
161
|
}
|
|
158
162
|
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Retrieves a practitioner profile by ID
|
|
166
|
+
*
|
|
167
|
+
* @param {Firestore} db - Firestore instance
|
|
168
|
+
* @param {string} practitionerId - ID of the practitioner to retrieve
|
|
169
|
+
* @returns {Promise<Practitioner | null>} A promise resolving to the practitioner profile or null if not found
|
|
170
|
+
*/
|
|
171
|
+
export const getPractitionerProfile = async (
|
|
172
|
+
db: Firestore,
|
|
173
|
+
practitionerId: string
|
|
174
|
+
): Promise<Practitioner | null> => {
|
|
175
|
+
try {
|
|
176
|
+
console.log(
|
|
177
|
+
`[getPractitionerProfile] Fetching practitioner with ID: ${practitionerId}`
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const practitionerRef = doc(db, PRACTITIONERS_COLLECTION, practitionerId);
|
|
181
|
+
const practitionerSnapshot = await getDoc(practitionerRef);
|
|
182
|
+
|
|
183
|
+
if (!practitionerSnapshot.exists()) {
|
|
184
|
+
console.log(
|
|
185
|
+
`[getPractitionerProfile] Practitioner with ID ${practitionerId} not found`
|
|
186
|
+
);
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const practitioner = practitionerSnapshot.data() as Practitioner;
|
|
191
|
+
console.log(
|
|
192
|
+
`[getPractitionerProfile] Successfully retrieved practitioner: ${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return practitioner;
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error(
|
|
198
|
+
`[getPractitionerProfile] Error fetching practitioner:`,
|
|
199
|
+
error
|
|
200
|
+
);
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Failed to retrieve practitioner: ${
|
|
203
|
+
error instanceof Error ? error.message : String(error)
|
|
204
|
+
}`
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Retrieves a practitioner profile by user reference ID
|
|
211
|
+
*
|
|
212
|
+
* @param {Firestore} db - Firestore instance
|
|
213
|
+
* @param {string} userRef - Firebase Auth user ID reference
|
|
214
|
+
* @returns {Promise<Practitioner | null>} A promise resolving to the practitioner profile or null if not found
|
|
215
|
+
*/
|
|
216
|
+
export const getPractitionerProfileByUserRef = async (
|
|
217
|
+
db: Firestore,
|
|
218
|
+
userRef: string
|
|
219
|
+
): Promise<Practitioner | null> => {
|
|
220
|
+
try {
|
|
221
|
+
console.log(
|
|
222
|
+
`[getPractitionerProfileByUserRef] Fetching practitioner with userRef: ${userRef}`
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const practitionersCollection = collection(db, PRACTITIONERS_COLLECTION);
|
|
226
|
+
const q = query(
|
|
227
|
+
practitionersCollection,
|
|
228
|
+
where("userRef", "==", userRef),
|
|
229
|
+
limit(1)
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const querySnapshot = await getDocs(q);
|
|
233
|
+
|
|
234
|
+
if (querySnapshot.empty) {
|
|
235
|
+
console.log(
|
|
236
|
+
`[getPractitionerProfileByUserRef] No practitioner found with userRef: ${userRef}`
|
|
237
|
+
);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const practitionerDoc = querySnapshot.docs[0];
|
|
242
|
+
const practitioner = practitionerDoc.data() as Practitioner;
|
|
243
|
+
|
|
244
|
+
console.log(
|
|
245
|
+
`[getPractitionerProfileByUserRef] Successfully retrieved practitioner: ${practitioner.basicInfo.firstName} ${practitioner.basicInfo.lastName}`
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
return practitioner;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(
|
|
251
|
+
`[getPractitionerProfileByUserRef] Error fetching practitioner:`,
|
|
252
|
+
error
|
|
253
|
+
);
|
|
254
|
+
throw new Error(
|
|
255
|
+
`Failed to retrieve practitioner by userRef: ${
|
|
256
|
+
error instanceof Error ? error.message : String(error)
|
|
257
|
+
}`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
@@ -94,6 +94,7 @@ export interface LinkedFormInfo {
|
|
|
94
94
|
title: string; // For display, usually from DocumentTemplate.title
|
|
95
95
|
isUserForm: boolean;
|
|
96
96
|
isRequired?: boolean;
|
|
97
|
+
sortingOrder?: number;
|
|
97
98
|
status: FilledDocumentStatus; // Status of the filled form (e.g., draft, completed, signed)
|
|
98
99
|
path: string; // Full Firestore path to the filled document (e.g., appointments/{aid}/user-forms/{fid})
|
|
99
100
|
submittedAt?: Timestamp;
|
|
@@ -34,7 +34,7 @@ export interface PatientMedicalInfo {
|
|
|
34
34
|
condition: BlockingCondition;
|
|
35
35
|
diagnosedAt: Timestamp;
|
|
36
36
|
isActive: boolean;
|
|
37
|
-
notes?: string;
|
|
37
|
+
notes?: string | null;
|
|
38
38
|
}[];
|
|
39
39
|
|
|
40
40
|
contraindications: {
|
|
@@ -42,7 +42,7 @@ export interface PatientMedicalInfo {
|
|
|
42
42
|
lastOccurrence: Timestamp;
|
|
43
43
|
frequency: "rare" | "occasional" | "frequent";
|
|
44
44
|
isActive: boolean;
|
|
45
|
-
notes?: string;
|
|
45
|
+
notes?: string | null;
|
|
46
46
|
}[];
|
|
47
47
|
|
|
48
48
|
allergies: Allergy[];
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { Timestamp } from "firebase/firestore";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
// Create a custom schema for Timestamp that properly handles both raw objects and Timestamp instances
|
|
5
|
+
export const timestampSchema = z
|
|
6
|
+
.union([
|
|
7
|
+
z.object({
|
|
8
|
+
seconds: z.number(),
|
|
9
|
+
nanoseconds: z.number(),
|
|
10
|
+
}),
|
|
11
|
+
z.instanceof(Timestamp),
|
|
12
|
+
])
|
|
13
|
+
.transform((data) => {
|
|
14
|
+
if (data instanceof Timestamp) {
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
return new Timestamp(data.seconds, data.nanoseconds);
|
|
18
|
+
});
|
|
8
19
|
|
|
9
20
|
// export const timestampSchema = z.instanceof(Timestamp);
|