@blackcode_sa/metaestetics-api 1.5.16 → 1.5.18

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.
@@ -8,47 +8,54 @@ import {
8
8
  increment,
9
9
  Firestore,
10
10
  Timestamp,
11
- } from "firebase/firestore";
12
- import {
13
- ref,
14
- uploadBytes,
15
- getDownloadURL,
16
- deleteObject,
17
- FirebaseStorage,
18
- } from "firebase/storage";
19
- import { z } from "zod";
11
+ collection,
12
+ query,
13
+ where,
14
+ getDocs,
15
+ QueryConstraint,
16
+ limit,
17
+ startAfter,
18
+ doc,
19
+ } from 'firebase/firestore';
20
+ import { ref, uploadBytes, getDownloadURL, deleteObject, FirebaseStorage } from 'firebase/storage';
21
+ import { z } from 'zod';
20
22
  import {
21
23
  PatientProfile,
22
24
  CreatePatientProfileData,
23
25
  Gender,
24
- } from "../../../types/patient";
26
+ SearchPatientsParams,
27
+ RequesterInfo,
28
+ PATIENTS_COLLECTION,
29
+ } from '../../../types/patient';
25
30
  import {
26
31
  patientProfileSchema,
27
32
  createPatientProfileSchema,
28
- } from "../../../validations/patient.schema";
33
+ searchPatientsSchema,
34
+ requesterInfoSchema,
35
+ } from '../../../validations/patient.schema';
29
36
  import {
30
37
  getPatientDocRef,
31
38
  getPatientDocRefByUserRef,
32
39
  initSensitiveInfoDocIfNotExists,
33
40
  getSensitiveInfoDocRef,
34
41
  getMedicalInfoDocRef,
35
- } from "./docs.utils";
36
- import { ensureMedicalInfoExists } from "./medical.utils";
37
- import { DEFAULT_MEDICAL_INFO } from "../../../types/patient/medical-info.types";
42
+ } from './docs.utils';
43
+ import { ensureMedicalInfoExists } from './medical.utils';
44
+ import { DEFAULT_MEDICAL_INFO } from '../../../types/patient/medical-info.types';
38
45
 
39
46
  // Funkcije za rad sa profilom
40
47
  export const createPatientProfileUtil = async (
41
48
  db: Firestore,
42
49
  data: CreatePatientProfileData,
43
- generateId: () => string
50
+ generateId: () => string,
44
51
  ): Promise<PatientProfile> => {
45
52
  try {
46
- console.log("[createPatientProfileUtil] Starting patient profile creation");
53
+ console.log('[createPatientProfileUtil] Starting patient profile creation');
47
54
  const validatedData = createPatientProfileSchema.parse(data);
48
55
  const patientId = generateId();
49
56
  console.log(`[createPatientProfileUtil] Generated patientId: ${patientId}`);
50
57
 
51
- const patientData: Omit<PatientProfile, "createdAt" | "updatedAt"> & {
58
+ const patientData: Omit<PatientProfile, 'createdAt' | 'updatedAt'> & {
52
59
  createdAt: ReturnType<typeof serverTimestamp>;
53
60
  updatedAt: ReturnType<typeof serverTimestamp>;
54
61
  } = {
@@ -65,6 +72,8 @@ export const createPatientProfileUtil = async (
65
72
  isVerified: validatedData.isVerified,
66
73
  doctors: validatedData.doctors || [],
67
74
  clinics: validatedData.clinics || [],
75
+ doctorIds: validatedData.doctors?.map((d) => d.userRef) || [],
76
+ clinicIds: validatedData.clinics?.map((c) => c.clinicId) || [],
68
77
  createdAt: serverTimestamp(),
69
78
  updatedAt: serverTimestamp(),
70
79
  };
@@ -84,21 +93,16 @@ export const createPatientProfileUtil = async (
84
93
  const sensitiveInfoCreated = await initSensitiveInfoDocIfNotExists(
85
94
  db,
86
95
  patientId,
87
- validatedData.userRef
96
+ validatedData.userRef,
88
97
  );
89
98
  console.log(
90
99
  `[createPatientProfileUtil] Sensitive info document creation result: ${
91
- sensitiveInfoCreated
92
- ? "Document already existed"
93
- : "New document created"
94
- }`
100
+ sensitiveInfoCreated ? 'Document already existed' : 'New document created'
101
+ }`,
95
102
  );
96
103
  sensitiveInfoSuccess = true;
97
104
  } catch (sensitiveError) {
98
- console.error(
99
- `[createPatientProfileUtil] Error creating sensitive info:`,
100
- sensitiveError
101
- );
105
+ console.error(`[createPatientProfileUtil] Error creating sensitive info:`, sensitiveError);
102
106
  // Don't throw the error, we'll try the direct method
103
107
  }
104
108
 
@@ -107,33 +111,21 @@ export const createPatientProfileUtil = async (
107
111
  let medicalInfoSuccess = false;
108
112
  try {
109
113
  await ensureMedicalInfoExists(db, patientId, validatedData.userRef);
110
- console.log(
111
- `[createPatientProfileUtil] Medical info document created successfully`
112
- );
114
+ console.log(`[createPatientProfileUtil] Medical info document created successfully`);
113
115
  medicalInfoSuccess = true;
114
116
  } catch (medicalError) {
115
- console.error(
116
- `[createPatientProfileUtil] Error creating medical info:`,
117
- medicalError
118
- );
117
+ console.error(`[createPatientProfileUtil] Error creating medical info:`, medicalError);
119
118
  // Don't throw the error, we'll try the direct method
120
119
  }
121
120
 
122
121
  // If either utility function failed, try the direct method
123
122
  if (!sensitiveInfoSuccess || !medicalInfoSuccess) {
124
- console.log(
125
- `[createPatientProfileUtil] Using fallback method to create documents`
126
- );
123
+ console.log(`[createPatientProfileUtil] Using fallback method to create documents`);
127
124
  try {
128
125
  await testCreateSubDocuments(db, patientId, validatedData.userRef);
129
- console.log(
130
- `[createPatientProfileUtil] Fallback method completed successfully`
131
- );
126
+ console.log(`[createPatientProfileUtil] Fallback method completed successfully`);
132
127
  } catch (fallbackError) {
133
- console.error(
134
- `[createPatientProfileUtil] Fallback method failed:`,
135
- fallbackError
136
- );
128
+ console.error(`[createPatientProfileUtil] Fallback method failed:`, fallbackError);
137
129
  // Still continue to return the patient profile
138
130
  }
139
131
  }
@@ -141,23 +133,16 @@ export const createPatientProfileUtil = async (
141
133
  console.log(`[createPatientProfileUtil] Verifying patient document exists`);
142
134
  const patientDoc = await getDoc(getPatientDocRef(db, patientId));
143
135
  if (!patientDoc.exists()) {
144
- console.error(
145
- `[createPatientProfileUtil] Patient document not found after creation`
146
- );
147
- throw new Error("Failed to create patient profile");
136
+ console.error(`[createPatientProfileUtil] Patient document not found after creation`);
137
+ throw new Error('Failed to create patient profile');
148
138
  }
149
139
 
150
- console.log(
151
- `[createPatientProfileUtil] Patient profile creation completed successfully`
152
- );
140
+ console.log(`[createPatientProfileUtil] Patient profile creation completed successfully`);
153
141
  return patientDoc.data() as PatientProfile;
154
142
  } catch (error) {
155
- console.error(
156
- `[createPatientProfileUtil] Error in patient profile creation:`,
157
- error
158
- );
143
+ console.error(`[createPatientProfileUtil] Error in patient profile creation:`, error);
159
144
  if (error instanceof z.ZodError) {
160
- throw new Error("Invalid patient data: " + error.message);
145
+ throw new Error('Invalid patient data: ' + error.message);
161
146
  }
162
147
  throw error;
163
148
  }
@@ -165,7 +150,7 @@ export const createPatientProfileUtil = async (
165
150
 
166
151
  export const getPatientProfileUtil = async (
167
152
  db: Firestore,
168
- patientId: string
153
+ patientId: string,
169
154
  ): Promise<PatientProfile | null> => {
170
155
  const patientDoc = await getDoc(getPatientDocRef(db, patientId));
171
156
  return patientDoc.exists() ? (patientDoc.data() as PatientProfile) : null;
@@ -173,7 +158,7 @@ export const getPatientProfileUtil = async (
173
158
 
174
159
  export const getPatientProfileByUserRefUtil = async (
175
160
  db: Firestore,
176
- userRef: string
161
+ userRef: string,
177
162
  ): Promise<PatientProfile | null> => {
178
163
  try {
179
164
  const docRef = await getPatientDocRefByUserRef(db, userRef);
@@ -188,7 +173,7 @@ export const getPatientProfileByUserRefUtil = async (
188
173
  export const addExpoTokenUtil = async (
189
174
  db: Firestore,
190
175
  patientId: string,
191
- token: string
176
+ token: string,
192
177
  ): Promise<void> => {
193
178
  await updateDoc(getPatientDocRef(db, patientId), {
194
179
  expoTokens: arrayUnion(token),
@@ -199,7 +184,7 @@ export const addExpoTokenUtil = async (
199
184
  export const removeExpoTokenUtil = async (
200
185
  db: Firestore,
201
186
  patientId: string,
202
- token: string
187
+ token: string,
203
188
  ): Promise<void> => {
204
189
  await updateDoc(getPatientDocRef(db, patientId), {
205
190
  expoTokens: arrayRemove(token),
@@ -210,10 +195,10 @@ export const removeExpoTokenUtil = async (
210
195
  export const addPointsUtil = async (
211
196
  db: Firestore,
212
197
  patientId: string,
213
- points: number
198
+ points: number,
214
199
  ): Promise<void> => {
215
200
  await updateDoc(getPatientDocRef(db, patientId), {
216
- "gamification.points": increment(points),
201
+ 'gamification.points': increment(points),
217
202
  updatedAt: serverTimestamp(),
218
203
  });
219
204
  };
@@ -221,7 +206,7 @@ export const addPointsUtil = async (
221
206
  export const updatePatientProfileUtil = async (
222
207
  db: Firestore,
223
208
  patientId: string,
224
- data: Partial<Omit<PatientProfile, "id" | "createdAt" | "updatedAt">>
209
+ data: Partial<Omit<PatientProfile, 'id' | 'createdAt' | 'updatedAt'>>,
225
210
  ): Promise<PatientProfile> => {
226
211
  try {
227
212
  const updateData = {
@@ -233,7 +218,7 @@ export const updatePatientProfileUtil = async (
233
218
 
234
219
  const updatedDoc = await getDoc(getPatientDocRef(db, patientId));
235
220
  if (!updatedDoc.exists()) {
236
- throw new Error("Patient profile not found after update");
221
+ throw new Error('Patient profile not found after update');
237
222
  }
238
223
 
239
224
  return updatedDoc.data() as PatientProfile;
@@ -246,23 +231,21 @@ export const updatePatientProfileUtil = async (
246
231
  export const updatePatientProfileByUserRefUtil = async (
247
232
  db: Firestore,
248
233
  userRef: string,
249
- data: Partial<Omit<PatientProfile, "id" | "createdAt" | "updatedAt">>
234
+ data: Partial<Omit<PatientProfile, 'id' | 'createdAt' | 'updatedAt'>>,
250
235
  ): Promise<PatientProfile> => {
251
236
  try {
252
237
  const docRef = await getPatientDocRefByUserRef(db, userRef);
253
238
  const patientDoc = await getDoc(docRef);
254
239
 
255
240
  if (!patientDoc.exists()) {
256
- throw new Error("Patient profile not found");
241
+ throw new Error('Patient profile not found');
257
242
  }
258
243
 
259
244
  const patientData = patientDoc.data() as PatientProfile;
260
245
  return updatePatientProfileUtil(db, patientData.id, data);
261
246
  } catch (error: unknown) {
262
247
  const errorMessage = error instanceof Error ? error.message : String(error);
263
- throw new Error(
264
- `Failed to update patient profile by user ref: ${errorMessage}`
265
- );
248
+ throw new Error(`Failed to update patient profile by user ref: ${errorMessage}`);
266
249
  }
267
250
  };
268
251
 
@@ -270,7 +253,7 @@ export const updatePatientProfileByUserRefUtil = async (
270
253
  export const uploadProfilePhotoUtil = async (
271
254
  storage: FirebaseStorage,
272
255
  patientId: string,
273
- file: File
256
+ file: File,
274
257
  ): Promise<string> => {
275
258
  const photoRef = ref(storage, `patient-photos/${patientId}/profile-photo`);
276
259
  await uploadBytes(photoRef, file);
@@ -281,11 +264,11 @@ export const updateProfilePhotoUtil = async (
281
264
  storage: FirebaseStorage,
282
265
  db: Firestore,
283
266
  patientId: string,
284
- file: File
267
+ file: File,
285
268
  ): Promise<string> => {
286
269
  // Prvo obrišemo staru sliku ako postoji
287
270
  const patientDoc = await getDoc(getPatientDocRef(db, patientId));
288
- if (!patientDoc.exists()) throw new Error("Patient profile not found");
271
+ if (!patientDoc.exists()) throw new Error('Patient profile not found');
289
272
 
290
273
  const patientData = patientDoc.data() as PatientProfile;
291
274
  if (patientData.profilePhoto) {
@@ -293,7 +276,7 @@ export const updateProfilePhotoUtil = async (
293
276
  const oldPhotoRef = ref(storage, patientData.profilePhoto);
294
277
  await deleteObject(oldPhotoRef);
295
278
  } catch (error) {
296
- console.warn("Failed to delete old profile photo:", error);
279
+ console.warn('Failed to delete old profile photo:', error);
297
280
  }
298
281
  }
299
282
 
@@ -312,10 +295,10 @@ export const updateProfilePhotoUtil = async (
312
295
  export const deleteProfilePhotoUtil = async (
313
296
  storage: FirebaseStorage,
314
297
  db: Firestore,
315
- patientId: string
298
+ patientId: string,
316
299
  ): Promise<void> => {
317
300
  const patientDoc = await getDoc(getPatientDocRef(db, patientId));
318
- if (!patientDoc.exists()) throw new Error("Patient profile not found");
301
+ if (!patientDoc.exists()) throw new Error('Patient profile not found');
319
302
 
320
303
  const patientData = patientDoc.data() as PatientProfile;
321
304
  if (patientData.profilePhoto) {
@@ -323,7 +306,7 @@ export const deleteProfilePhotoUtil = async (
323
306
  const photoRef = ref(storage, patientData.profilePhoto);
324
307
  await deleteObject(photoRef);
325
308
  } catch (error) {
326
- console.warn("Failed to delete profile photo:", error);
309
+ console.warn('Failed to delete profile photo:', error);
327
310
  }
328
311
  }
329
312
 
@@ -340,45 +323,39 @@ export const deleteProfilePhotoUtil = async (
340
323
  export const testCreateSubDocuments = async (
341
324
  db: Firestore,
342
325
  patientId: string,
343
- userRef: string
326
+ userRef: string,
344
327
  ): Promise<void> => {
345
328
  console.log(
346
- `[testCreateSubDocuments] Starting test for patientId: ${patientId}, userRef: ${userRef}`
329
+ `[testCreateSubDocuments] Starting test for patientId: ${patientId}, userRef: ${userRef}`,
347
330
  );
348
331
 
349
332
  try {
350
333
  // Test sensitive info creation
351
334
  console.log(`[testCreateSubDocuments] Testing sensitive info creation`);
352
335
  const sensitiveInfoRef = getSensitiveInfoDocRef(db, patientId);
353
- console.log(
354
- `[testCreateSubDocuments] Sensitive info path: ${sensitiveInfoRef.path}`
355
- );
336
+ console.log(`[testCreateSubDocuments] Sensitive info path: ${sensitiveInfoRef.path}`);
356
337
 
357
338
  const defaultSensitiveInfo = {
358
339
  patientId,
359
340
  userRef,
360
- photoUrl: "",
361
- firstName: "Name",
362
- lastName: "Surname",
341
+ photoUrl: '',
342
+ firstName: 'Name',
343
+ lastName: 'Surname',
363
344
  dateOfBirth: Timestamp.now(),
364
345
  gender: Gender.PREFER_NOT_TO_SAY,
365
- email: "test@example.com",
366
- phoneNumber: "",
346
+ email: 'test@example.com',
347
+ phoneNumber: '',
367
348
  createdAt: Timestamp.now(),
368
349
  updatedAt: Timestamp.now(),
369
350
  };
370
351
 
371
352
  await setDoc(sensitiveInfoRef, defaultSensitiveInfo);
372
- console.log(
373
- `[testCreateSubDocuments] Sensitive info document created directly`
374
- );
353
+ console.log(`[testCreateSubDocuments] Sensitive info document created directly`);
375
354
 
376
355
  // Test medical info creation
377
356
  console.log(`[testCreateSubDocuments] Testing medical info creation`);
378
357
  const medicalInfoRef = getMedicalInfoDocRef(db, patientId);
379
- console.log(
380
- `[testCreateSubDocuments] Medical info path: ${medicalInfoRef.path}`
381
- );
358
+ console.log(`[testCreateSubDocuments] Medical info path: ${medicalInfoRef.path}`);
382
359
 
383
360
  const defaultMedicalInfo = {
384
361
  ...DEFAULT_MEDICAL_INFO,
@@ -388,9 +365,7 @@ export const testCreateSubDocuments = async (
388
365
  };
389
366
 
390
367
  await setDoc(medicalInfoRef, defaultMedicalInfo);
391
- console.log(
392
- `[testCreateSubDocuments] Medical info document created directly`
393
- );
368
+ console.log(`[testCreateSubDocuments] Medical info document created directly`);
394
369
 
395
370
  console.log(`[testCreateSubDocuments] Test completed successfully`);
396
371
  } catch (error) {
@@ -398,3 +373,140 @@ export const testCreateSubDocuments = async (
398
373
  throw error;
399
374
  }
400
375
  };
376
+
377
+ /**
378
+ * Searches for patient profiles based on clinic and/or practitioner association.
379
+ * Applies security checks based on the requester's role and associations.
380
+ *
381
+ * @param {Firestore} db - Firestore instance.
382
+ * @param {SearchPatientsParams} params - Search criteria (clinicId, practitionerId).
383
+ * @param {RequesterInfo} requester - Information about the user performing the search.
384
+ * @returns {Promise<PatientProfile[]>} A promise resolving to an array of matching patient profiles.
385
+ */
386
+ export const searchPatientsUtil = async (
387
+ db: Firestore,
388
+ params: SearchPatientsParams,
389
+ requester: RequesterInfo,
390
+ ): Promise<PatientProfile[]> => {
391
+ // Validate input
392
+ searchPatientsSchema.parse(params);
393
+ requesterInfoSchema.parse(requester);
394
+
395
+ const constraints: QueryConstraint[] = [];
396
+ const patientsCollectionRef = collection(db, PATIENTS_COLLECTION);
397
+
398
+ // --- Security Checks & Initial Filtering ---
399
+
400
+ if (requester.role === 'clinic_admin') {
401
+ // Clinic admin can only search within their own clinic
402
+ if (!requester.associatedClinicId) {
403
+ throw new Error('Associated clinic ID is required for clinic admin search.');
404
+ }
405
+ // If the search params specify a different clinic, it's an invalid request for this admin.
406
+ if (params.clinicId && params.clinicId !== requester.associatedClinicId) {
407
+ console.warn(
408
+ `Clinic admin (${requester.id}) attempted to search outside their associated clinic (${requester.associatedClinicId})`,
409
+ );
410
+ return []; // Or throw an error
411
+ }
412
+
413
+ // **Mandatory filter**: Ensure patients belong to the admin's clinic.
414
+ constraints.push(where('clinicIds', 'array-contains', requester.associatedClinicId));
415
+
416
+ // Optional filter: If practitionerId is also provided, filter by that practitioner *within the admin's clinic*.
417
+ if (params.practitionerId) {
418
+ constraints.push(where('doctorIds', 'array-contains', params.practitionerId));
419
+ // We might need an additional check here if the practitioner MUST work at the admin's clinic.
420
+ // This would require fetching practitioner data or having denormalized clinic IDs on the practitioner.
421
+ }
422
+ } else if (requester.role === 'practitioner') {
423
+ // Practitioner can only search for their own patients.
424
+ if (!requester.associatedPractitionerId) {
425
+ throw new Error('Associated practitioner ID is required for practitioner search.');
426
+ }
427
+ // If the search params specify a different practitioner, it's invalid.
428
+ if (params.practitionerId && params.practitionerId !== requester.associatedPractitionerId) {
429
+ console.warn(
430
+ `Practitioner (${requester.id}) attempted to search for patients of another practitioner (${params.practitionerId})`,
431
+ );
432
+ return []; // Or throw an error
433
+ }
434
+
435
+ // **Mandatory filter**: Ensure patients are associated with this practitioner.
436
+ constraints.push(where('doctorIds', 'array-contains', requester.associatedPractitionerId));
437
+
438
+ // Optional filter: If clinicId is provided, filter by patients of this practitioner *at that specific clinic*.
439
+ if (params.clinicId) {
440
+ constraints.push(where('clinicIds', 'array-contains', params.clinicId));
441
+ // Similar to above, we might need to check if the practitioner actually works at this clinic.
442
+ }
443
+ } else {
444
+ // Should not happen due to validation, but good practice to handle.
445
+ throw new Error('Invalid requester role.');
446
+ }
447
+
448
+ // --- Execute Query ---
449
+ try {
450
+ const finalQuery = query(patientsCollectionRef, ...constraints);
451
+ const querySnapshot = await getDocs(finalQuery);
452
+
453
+ const patients = querySnapshot.docs.map((doc) => doc.data() as PatientProfile);
454
+
455
+ console.log(`[searchPatientsUtil] Found ${patients.length} patients matching criteria.`);
456
+ return patients;
457
+ } catch (error) {
458
+ console.error('[searchPatientsUtil] Error searching patients:', error);
459
+ // Consider logging more details or re-throwing a specific error type
460
+ return []; // Return empty array on error
461
+ }
462
+ };
463
+
464
+ /**
465
+ * Retrieves all patient profiles from the database.
466
+ *
467
+ * @param {Firestore} db - Firestore instance
468
+ * @param {Object} options - Optional parameters for pagination
469
+ * @param {number} options.limit - Maximum number of profiles to return
470
+ * @param {string} options.startAfter - The ID of the document to start after (for pagination)
471
+ * @returns {Promise<PatientProfile[]>} A promise resolving to an array of all patient profiles
472
+ */
473
+ export const getAllPatientsUtil = async (
474
+ db: Firestore,
475
+ options?: { limit?: number; startAfter?: string },
476
+ ): Promise<PatientProfile[]> => {
477
+ try {
478
+ console.log(`[getAllPatientsUtil] Fetching patients with options:`, options);
479
+
480
+ const patientsCollection = collection(db, PATIENTS_COLLECTION);
481
+
482
+ let q = query(patientsCollection);
483
+
484
+ // Apply pagination if needed
485
+ if (options?.limit) {
486
+ q = query(q, limit(options.limit));
487
+ }
488
+
489
+ // If startAfter is provided, get that document and use it for pagination
490
+ if (options?.startAfter) {
491
+ const startAfterDoc = await getDoc(doc(db, PATIENTS_COLLECTION, options.startAfter));
492
+ if (startAfterDoc.exists()) {
493
+ q = query(q, startAfter(startAfterDoc));
494
+ }
495
+ }
496
+
497
+ const patientsSnapshot = await getDocs(q);
498
+
499
+ const patients: PatientProfile[] = [];
500
+ patientsSnapshot.forEach((doc) => {
501
+ patients.push(doc.data() as PatientProfile);
502
+ });
503
+
504
+ console.log(`[getAllPatientsUtil] Found ${patients.length} patients`);
505
+ return patients;
506
+ } catch (error) {
507
+ console.error(`[getAllPatientsUtil] Error fetching patients:`, error);
508
+ throw new Error(
509
+ `Failed to retrieve patients: ${error instanceof Error ? error.message : String(error)}`,
510
+ );
511
+ }
512
+ };
@@ -185,3 +185,44 @@ export interface UpdateAppointmentParams {
185
185
  * Collection names for calendar
186
186
  */
187
187
  export const CALENDAR_COLLECTION = "calendar";
188
+
189
+ /**
190
+ * Enum for specifying the primary search location for calendar events
191
+ */
192
+ export enum SearchLocationEnum {
193
+ PRACTITIONER = "practitioner",
194
+ PATIENT = "patient",
195
+ CLINIC = "clinic", // Represents searching events associated with a clinic
196
+ }
197
+
198
+ /**
199
+ * Interface for defining a date range
200
+ */
201
+ export interface DateRange {
202
+ start: Timestamp;
203
+ end: Timestamp;
204
+ }
205
+
206
+ /**
207
+ * Interface for general calendar event search parameters
208
+ */
209
+ export interface SearchCalendarEventsParams {
210
+ /** The primary location to search within (practitioner, patient, or clinic calendar). */
211
+ searchLocation: SearchLocationEnum;
212
+ /** The ID of the entity (practitioner, patient, or clinic) whose calendar/events are being searched. */
213
+ entityId: string;
214
+ /** Optional filter for clinic ID. If searchLocation is CLINIC, this is implicitly applied using entityId. */
215
+ clinicId?: string;
216
+ /** Optional filter for practitioner ID. */
217
+ practitionerId?: string;
218
+ /** Optional filter for patient ID. */
219
+ patientId?: string;
220
+ /** Optional filter for procedure ID. */
221
+ procedureId?: string;
222
+ /** Optional filter for a specific date range (based on event start time). */
223
+ dateRange?: DateRange;
224
+ /** Optional filter for event status. */
225
+ eventStatus?: CalendarEventStatus;
226
+ /** Optional filter for event type. */
227
+ eventType?: CalendarEventType;
228
+ }
@@ -172,6 +172,8 @@ export interface PatientProfile {
172
172
  dateOfBirth?: Timestamp | null;
173
173
  doctors: PatientDoctor[]; // Lista doktora pacijenta
174
174
  clinics: PatientClinic[]; // Lista klinika pacijenta
175
+ doctorIds: string[]; // Denormalized array for querying
176
+ clinicIds: string[]; // Denormalized array for querying
175
177
  createdAt: Timestamp;
176
178
  updatedAt: Timestamp;
177
179
  }
@@ -188,14 +190,42 @@ export interface CreatePatientProfileData {
188
190
  isVerified: boolean;
189
191
  doctors?: PatientDoctor[];
190
192
  clinics?: PatientClinic[];
193
+ doctorIds?: string[]; // Initialize as empty or with initial doctors
194
+ clinicIds?: string[]; // Initialize as empty or with initial clinics
191
195
  }
192
196
 
193
197
  /**
194
198
  * Tip za ažuriranje Patient profila
195
199
  */
196
200
  export interface UpdatePatientProfileData
197
- extends Partial<CreatePatientProfileData> {
201
+ extends Partial<Omit<PatientProfile, "id" | "createdAt" | "updatedAt">> {
202
+ // Use Omit to exclude base fields
198
203
  updatedAt?: FieldValue;
204
+ // Note: doctors, clinics, doctorIds, clinicIds should ideally be updated via specific methods (add/removeDoctor/Clinic)
205
+ }
206
+
207
+ /**
208
+ * Parameters for searching patient profiles.
209
+ */
210
+ export interface SearchPatientsParams {
211
+ /** Optional: Filter patients associated with this clinic ID. */
212
+ clinicId?: string;
213
+ /** Optional: Filter patients associated with this practitioner ID. */
214
+ practitionerId?: string;
215
+ }
216
+
217
+ /**
218
+ * Information about the entity requesting the patient search.
219
+ */
220
+ export interface RequesterInfo {
221
+ /** ID of the clinic admin user or practitioner user making the request. */
222
+ id: string;
223
+ /** Role of the requester, determining the search context. */
224
+ role: "clinic_admin" | "practitioner";
225
+ /** If role is 'clinic_admin', this is the associated clinic ID. */
226
+ associatedClinicId?: string;
227
+ /** If role is 'practitioner', this is the associated practitioner profile ID. */
228
+ associatedPractitionerId?: string;
199
229
  }
200
230
 
201
231
  export * from "./medical-info.types";