@blackcode_sa/metaestetics-api 1.7.2 → 1.7.4
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 +41 -8
- package/dist/admin/index.d.ts +41 -8
- package/dist/admin/index.js +621 -109
- package/dist/admin/index.mjs +621 -109
- package/dist/index.d.mts +281 -342
- package/dist/index.d.ts +281 -342
- package/dist/index.js +1178 -1124
- package/dist/index.mjs +388 -334
- package/package.json +1 -1
- package/src/admin/aggregation/appointment/appointment.aggregation.service.ts +553 -112
- package/src/admin/calendar/calendar.admin.service.ts +230 -65
- package/src/services/calendar/calendar-refactored.service.ts +2 -0
- package/src/services/patient/utils/medical.utils.ts +26 -23
- package/src/services/patient/utils/practitioner.utils.ts +102 -0
- package/src/types/calendar/index.ts +1 -0
- package/src/types/patient/medical-info.types.ts +2 -2
- package/src/validations/common.schema.ts +15 -4
|
@@ -42,47 +42,93 @@ export class CalendarAdminService {
|
|
|
42
42
|
);
|
|
43
43
|
const batch = this.db.batch();
|
|
44
44
|
const serverTimestamp = admin.firestore.FieldValue.serverTimestamp();
|
|
45
|
+
let updatesAdded = 0;
|
|
45
46
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
// We need a robust way to find these if they aren't directly on the appointment object.
|
|
49
|
-
// For now, assuming we can query them or they are added to the Appointment type.
|
|
47
|
+
// Get the shared calendar event ID
|
|
48
|
+
const calendarEventId = appointment.calendarEventId;
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const practitionerCalendarEventPath = `${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${appointment.calendarEventId}`;
|
|
59
|
-
// Example paths, these need to be accurate based on your data model:
|
|
60
|
-
// const patientCalendarEventPath = `${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${appointment.patientCalendarEventId}`;
|
|
61
|
-
// const clinicCalendarEventPath = `${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${appointment.clinicCalendarEventId}`;
|
|
50
|
+
if (!calendarEventId) {
|
|
51
|
+
Logger.warn(
|
|
52
|
+
`[CalendarAdminService] Missing calendar event ID for appointment ${appointment.id}`
|
|
53
|
+
);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
62
56
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const practitionerEventRef = this.db.doc(
|
|
57
|
+
// Update practitioner calendar event
|
|
58
|
+
if (appointment.practitionerId) {
|
|
59
|
+
const practitionerEventRef = this.db.doc(
|
|
60
|
+
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
61
|
+
);
|
|
66
62
|
batch.update(practitionerEventRef, {
|
|
67
63
|
status: newStatus,
|
|
68
64
|
updatedAt: serverTimestamp,
|
|
69
65
|
});
|
|
66
|
+
updatesAdded++;
|
|
67
|
+
Logger.debug(
|
|
68
|
+
`[CalendarAdminService] Added practitioner calendar event status update to batch`
|
|
69
|
+
);
|
|
70
|
+
} else {
|
|
71
|
+
Logger.warn(
|
|
72
|
+
`[CalendarAdminService] Missing practitioner ID for appointment ${appointment.id}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Update patient calendar event
|
|
77
|
+
if (appointment.patientId) {
|
|
78
|
+
const patientEventRef = this.db.doc(
|
|
79
|
+
`${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
80
|
+
);
|
|
81
|
+
batch.update(patientEventRef, {
|
|
82
|
+
status: newStatus,
|
|
83
|
+
updatedAt: serverTimestamp,
|
|
84
|
+
});
|
|
85
|
+
updatesAdded++;
|
|
86
|
+
Logger.debug(
|
|
87
|
+
`[CalendarAdminService] Added patient calendar event status update to batch`
|
|
88
|
+
);
|
|
89
|
+
} else {
|
|
90
|
+
Logger.warn(
|
|
91
|
+
`[CalendarAdminService] Missing patient ID for appointment ${appointment.id}`
|
|
92
|
+
);
|
|
70
93
|
}
|
|
71
|
-
// Add similar updates for patient and clinic calendar events once their refs are confirmed
|
|
72
|
-
// if (appointment.patientCalendarEventId) { ... }
|
|
73
|
-
// if (appointment.clinicCalendarEventId) { ... }
|
|
74
94
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
// Update clinic calendar event
|
|
96
|
+
if (appointment.clinicBranchId) {
|
|
97
|
+
const clinicEventRef = this.db.doc(
|
|
98
|
+
`${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
99
|
+
);
|
|
100
|
+
batch.update(clinicEventRef, {
|
|
101
|
+
status: newStatus,
|
|
102
|
+
updatedAt: serverTimestamp,
|
|
103
|
+
});
|
|
104
|
+
updatesAdded++;
|
|
105
|
+
Logger.debug(
|
|
106
|
+
`[CalendarAdminService] Added clinic calendar event status update to batch`
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
Logger.warn(
|
|
110
|
+
`[CalendarAdminService] Missing clinic ID for appointment ${appointment.id}`
|
|
79
111
|
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Only commit if we have updates to make
|
|
115
|
+
if (updatesAdded > 0) {
|
|
116
|
+
try {
|
|
117
|
+
await batch.commit();
|
|
118
|
+
Logger.info(
|
|
119
|
+
`[CalendarAdminService] Successfully updated ${updatesAdded} calendar event statuses for appointment ${appointment.id}.`
|
|
120
|
+
);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
Logger.error(
|
|
123
|
+
`[CalendarAdminService] Error updating calendar event statuses for appointment ${appointment.id}:`,
|
|
124
|
+
error
|
|
125
|
+
);
|
|
126
|
+
throw error; // Re-throw to allow caller to handle
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
Logger.warn(
|
|
130
|
+
`[CalendarAdminService] No calendar event status updates were made for appointment ${appointment.id}`
|
|
84
131
|
);
|
|
85
|
-
// Decide on error handling: re-throw, or log and continue?
|
|
86
132
|
}
|
|
87
133
|
}
|
|
88
134
|
|
|
@@ -91,51 +137,113 @@ export class CalendarAdminService {
|
|
|
91
137
|
* associated with a given appointment.
|
|
92
138
|
*
|
|
93
139
|
* @param appointment - The appointment object.
|
|
94
|
-
* @param newEventTime - The new CalendarEventTime object (using
|
|
140
|
+
* @param newEventTime - The new CalendarEventTime object (using admin.firestore.Timestamp).
|
|
95
141
|
* @returns {Promise<void>} A promise that resolves when all updates are attempted.
|
|
96
142
|
*/
|
|
97
143
|
async updateAppointmentCalendarEventsTime(
|
|
98
144
|
appointment: Appointment,
|
|
99
|
-
newEventTime:
|
|
145
|
+
newEventTime: {
|
|
146
|
+
start: admin.firestore.Timestamp;
|
|
147
|
+
end: admin.firestore.Timestamp;
|
|
148
|
+
}
|
|
100
149
|
): Promise<void> {
|
|
101
150
|
Logger.info(
|
|
102
151
|
`[CalendarAdminService] Updating calendar event times for appointment ${appointment.id}`
|
|
103
152
|
);
|
|
104
153
|
const batch = this.db.batch();
|
|
105
154
|
const serverTimestamp = admin.firestore.FieldValue.serverTimestamp();
|
|
155
|
+
let updatesAdded = 0;
|
|
156
|
+
|
|
157
|
+
// Get the shared calendar event ID
|
|
158
|
+
const calendarEventId = appointment.calendarEventId;
|
|
106
159
|
|
|
107
|
-
|
|
160
|
+
if (!calendarEventId) {
|
|
161
|
+
Logger.warn(
|
|
162
|
+
`[CalendarAdminService] Missing calendar event ID for appointment ${appointment.id}`
|
|
163
|
+
);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Use the timestamps directly since they are already admin.firestore.Timestamp
|
|
108
168
|
const firestoreCompatibleEventTime = {
|
|
109
|
-
start:
|
|
110
|
-
|
|
111
|
-
admin.firestore.Timestamp.now(),
|
|
112
|
-
end:
|
|
113
|
-
TimestampUtils.clientToAdmin(newEventTime.end) ||
|
|
114
|
-
admin.firestore.Timestamp.now(),
|
|
169
|
+
start: newEventTime.start,
|
|
170
|
+
end: newEventTime.end,
|
|
115
171
|
};
|
|
116
172
|
|
|
117
|
-
//
|
|
118
|
-
if (appointment.
|
|
119
|
-
// Practitioner event
|
|
173
|
+
// Update practitioner calendar event
|
|
174
|
+
if (appointment.practitionerId) {
|
|
120
175
|
const practitionerEventRef = this.db.doc(
|
|
121
|
-
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${
|
|
176
|
+
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
122
177
|
);
|
|
123
178
|
batch.update(practitionerEventRef, {
|
|
124
179
|
eventTime: firestoreCompatibleEventTime,
|
|
125
180
|
updatedAt: serverTimestamp,
|
|
126
181
|
});
|
|
182
|
+
updatesAdded++;
|
|
183
|
+
Logger.debug(
|
|
184
|
+
`[CalendarAdminService] Added practitioner calendar event time update to batch`
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
Logger.warn(
|
|
188
|
+
`[CalendarAdminService] Missing practitioner ID for appointment ${appointment.id}`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Update patient calendar event
|
|
193
|
+
if (appointment.patientId) {
|
|
194
|
+
const patientEventRef = this.db.doc(
|
|
195
|
+
`${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
196
|
+
);
|
|
197
|
+
batch.update(patientEventRef, {
|
|
198
|
+
eventTime: firestoreCompatibleEventTime,
|
|
199
|
+
updatedAt: serverTimestamp,
|
|
200
|
+
});
|
|
201
|
+
updatesAdded++;
|
|
202
|
+
Logger.debug(
|
|
203
|
+
`[CalendarAdminService] Added patient calendar event time update to batch`
|
|
204
|
+
);
|
|
205
|
+
} else {
|
|
206
|
+
Logger.warn(
|
|
207
|
+
`[CalendarAdminService] Missing patient ID for appointment ${appointment.id}`
|
|
208
|
+
);
|
|
127
209
|
}
|
|
128
|
-
// Add similar updates for patient and clinic calendar events
|
|
129
210
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
211
|
+
// Update clinic calendar event
|
|
212
|
+
if (appointment.clinicBranchId) {
|
|
213
|
+
const clinicEventRef = this.db.doc(
|
|
214
|
+
`${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
134
215
|
);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
216
|
+
batch.update(clinicEventRef, {
|
|
217
|
+
eventTime: firestoreCompatibleEventTime,
|
|
218
|
+
updatedAt: serverTimestamp,
|
|
219
|
+
});
|
|
220
|
+
updatesAdded++;
|
|
221
|
+
Logger.debug(
|
|
222
|
+
`[CalendarAdminService] Added clinic calendar event time update to batch`
|
|
223
|
+
);
|
|
224
|
+
} else {
|
|
225
|
+
Logger.warn(
|
|
226
|
+
`[CalendarAdminService] Missing clinic ID for appointment ${appointment.id}`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Only commit if we have updates to make
|
|
231
|
+
if (updatesAdded > 0) {
|
|
232
|
+
try {
|
|
233
|
+
await batch.commit();
|
|
234
|
+
Logger.info(
|
|
235
|
+
`[CalendarAdminService] Successfully updated ${updatesAdded} calendar event times for appointment ${appointment.id}.`
|
|
236
|
+
);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
Logger.error(
|
|
239
|
+
`[CalendarAdminService] Error updating calendar event times for appointment ${appointment.id}:`,
|
|
240
|
+
error
|
|
241
|
+
);
|
|
242
|
+
throw error; // Re-throw to allow caller to handle
|
|
243
|
+
}
|
|
244
|
+
} else {
|
|
245
|
+
Logger.warn(
|
|
246
|
+
`[CalendarAdminService] No calendar event time updates were made for appointment ${appointment.id}`
|
|
139
247
|
);
|
|
140
248
|
}
|
|
141
249
|
}
|
|
@@ -154,26 +262,83 @@ export class CalendarAdminService {
|
|
|
154
262
|
`[CalendarAdminService] Deleting calendar events for appointment ${appointment.id}`
|
|
155
263
|
);
|
|
156
264
|
const batch = this.db.batch();
|
|
265
|
+
let deletesAdded = 0;
|
|
157
266
|
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
267
|
+
// Get the shared calendar event ID
|
|
268
|
+
const calendarEventId = appointment.calendarEventId;
|
|
269
|
+
|
|
270
|
+
if (!calendarEventId) {
|
|
271
|
+
Logger.warn(
|
|
272
|
+
`[CalendarAdminService] Missing calendar event ID for appointment ${appointment.id}`
|
|
273
|
+
);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Delete practitioner calendar event
|
|
278
|
+
if (appointment.practitionerId) {
|
|
161
279
|
const practitionerEventRef = this.db.doc(
|
|
162
|
-
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${
|
|
280
|
+
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
163
281
|
);
|
|
164
282
|
batch.delete(practitionerEventRef);
|
|
283
|
+
deletesAdded++;
|
|
284
|
+
Logger.debug(
|
|
285
|
+
`[CalendarAdminService] Added practitioner calendar event deletion to batch`
|
|
286
|
+
);
|
|
287
|
+
} else {
|
|
288
|
+
Logger.warn(
|
|
289
|
+
`[CalendarAdminService] Missing practitioner ID for appointment ${appointment.id}`
|
|
290
|
+
);
|
|
165
291
|
}
|
|
166
|
-
// Add similar deletes for patient and clinic calendar events
|
|
167
292
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
293
|
+
// Delete patient calendar event
|
|
294
|
+
if (appointment.patientId) {
|
|
295
|
+
const patientEventRef = this.db.doc(
|
|
296
|
+
`${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
297
|
+
);
|
|
298
|
+
batch.delete(patientEventRef);
|
|
299
|
+
deletesAdded++;
|
|
300
|
+
Logger.debug(
|
|
301
|
+
`[CalendarAdminService] Added patient calendar event deletion to batch`
|
|
302
|
+
);
|
|
303
|
+
} else {
|
|
304
|
+
Logger.warn(
|
|
305
|
+
`[CalendarAdminService] Missing patient ID for appointment ${appointment.id}`
|
|
172
306
|
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Delete clinic calendar event
|
|
310
|
+
if (appointment.clinicBranchId) {
|
|
311
|
+
const clinicEventRef = this.db.doc(
|
|
312
|
+
`${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
313
|
+
);
|
|
314
|
+
batch.delete(clinicEventRef);
|
|
315
|
+
deletesAdded++;
|
|
316
|
+
Logger.debug(
|
|
317
|
+
`[CalendarAdminService] Added clinic calendar event deletion to batch`
|
|
318
|
+
);
|
|
319
|
+
} else {
|
|
320
|
+
Logger.warn(
|
|
321
|
+
`[CalendarAdminService] Missing clinic ID for appointment ${appointment.id}`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Only commit if we have deletions to make
|
|
326
|
+
if (deletesAdded > 0) {
|
|
327
|
+
try {
|
|
328
|
+
await batch.commit();
|
|
329
|
+
Logger.info(
|
|
330
|
+
`[CalendarAdminService] Successfully deleted ${deletesAdded} calendar events for appointment ${appointment.id}.`
|
|
331
|
+
);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
Logger.error(
|
|
334
|
+
`[CalendarAdminService] Error deleting calendar events for appointment ${appointment.id}:`,
|
|
335
|
+
error
|
|
336
|
+
);
|
|
337
|
+
throw error; // Re-throw to allow caller to handle
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
Logger.warn(
|
|
341
|
+
`[CalendarAdminService] No calendar event deletions were made for appointment ${appointment.id}`
|
|
177
342
|
);
|
|
178
343
|
}
|
|
179
344
|
}
|
|
@@ -1142,6 +1142,7 @@ export class CalendarServiceV2 extends BaseService {
|
|
|
1142
1142
|
CalendarEventStatus.CANCELED,
|
|
1143
1143
|
CalendarEventStatus.COMPLETED,
|
|
1144
1144
|
CalendarEventStatus.RESCHEDULED,
|
|
1145
|
+
CalendarEventStatus.NO_SHOW,
|
|
1145
1146
|
],
|
|
1146
1147
|
[CalendarEventStatus.REJECTED]: [],
|
|
1147
1148
|
[CalendarEventStatus.CANCELED]: [],
|
|
@@ -1150,6 +1151,7 @@ export class CalendarServiceV2 extends BaseService {
|
|
|
1150
1151
|
CalendarEventStatus.CANCELED,
|
|
1151
1152
|
],
|
|
1152
1153
|
[CalendarEventStatus.COMPLETED]: [],
|
|
1154
|
+
[CalendarEventStatus.NO_SHOW]: [],
|
|
1153
1155
|
};
|
|
1154
1156
|
|
|
1155
1157
|
// Check if transition is valid
|
|
@@ -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
|
+
};
|
|
@@ -27,6 +27,7 @@ export enum CalendarEventStatus {
|
|
|
27
27
|
CANCELED = "canceled", // When event is canceled by the patient
|
|
28
28
|
RESCHEDULED = "rescheduled", // When event is rescheduled by the clinic administrator
|
|
29
29
|
COMPLETED = "completed", // When event is completed
|
|
30
|
+
NO_SHOW = "no_show", // When event is no-show by the patient
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -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);
|