@blackcode_sa/metaestetics-api 1.5.15 → 1.5.17
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/backoffice/index.d.mts +2 -2
- package/dist/backoffice/index.d.ts +2 -2
- package/dist/index.d.mts +232 -38
- package/dist/index.d.ts +232 -38
- package/dist/index.js +698 -342
- package/dist/index.mjs +942 -570
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/services/calendar/calendar-refactored.service.ts +147 -0
- package/src/services/calendar/utils/calendar-event.utils.ts +136 -0
- package/src/services/calendar/utils/index.ts +5 -5
- package/src/services/clinic/utils/clinic-group.utils.ts +3 -0
- package/src/services/patient/patient.service.ts +29 -0
- package/src/services/patient/utils/medical-stuff.utils.ts +69 -18
- package/src/services/patient/utils/profile.utils.ts +116 -0
- package/src/types/calendar/index.ts +41 -0
- package/src/types/clinic/index.ts +2 -0
- package/src/types/patient/index.ts +31 -1
- package/src/types/practitioner/index.ts +13 -14
- package/src/validations/clinic.schema.ts +2 -0
- package/src/validations/patient.schema.ts +43 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -16,6 +16,9 @@ import {
|
|
|
16
16
|
TimeSlot,
|
|
17
17
|
CreateAppointmentParams,
|
|
18
18
|
UpdateAppointmentParams,
|
|
19
|
+
SearchCalendarEventsParams,
|
|
20
|
+
SearchLocationEnum,
|
|
21
|
+
DateRange,
|
|
19
22
|
} from "../../types/calendar";
|
|
20
23
|
import {
|
|
21
24
|
PRACTITIONERS_COLLECTION,
|
|
@@ -42,6 +45,9 @@ import {
|
|
|
42
45
|
getDocs,
|
|
43
46
|
setDoc,
|
|
44
47
|
updateDoc,
|
|
48
|
+
QueryConstraint,
|
|
49
|
+
CollectionReference,
|
|
50
|
+
DocumentData,
|
|
45
51
|
} from "firebase/firestore";
|
|
46
52
|
import {
|
|
47
53
|
createAppointmentSchema,
|
|
@@ -54,6 +60,7 @@ import {
|
|
|
54
60
|
updateAppointmentUtil,
|
|
55
61
|
deleteAppointmentUtil,
|
|
56
62
|
} from "./utils/appointment.utils";
|
|
63
|
+
import { searchCalendarEventsUtil } from "./utils/calendar-event.utils";
|
|
57
64
|
import { SyncedCalendarsService } from "./synced-calendars.service";
|
|
58
65
|
import { Timestamp as FirestoreTimestamp } from "firebase-admin/firestore";
|
|
59
66
|
|
|
@@ -776,6 +783,146 @@ export class CalendarServiceV2 extends BaseService {
|
|
|
776
783
|
*/
|
|
777
784
|
}
|
|
778
785
|
|
|
786
|
+
/**
|
|
787
|
+
* Searches for calendar events based on specified criteria.
|
|
788
|
+
*
|
|
789
|
+
* @param {SearchCalendarEventsParams} params - The search parameters.
|
|
790
|
+
* @param {SearchLocationEnum} params.searchLocation - The primary location to search (practitioner, patient, or clinic).
|
|
791
|
+
* @param {string} params.entityId - The ID of the entity (practitioner, patient, or clinic) to search within/for.
|
|
792
|
+
* @param {string} [params.clinicId] - Optional clinic ID to filter by.
|
|
793
|
+
* @param {string} [params.practitionerId] - Optional practitioner ID to filter by.
|
|
794
|
+
* @param {string} [params.patientId] - Optional patient ID to filter by.
|
|
795
|
+
* @param {string} [params.procedureId] - Optional procedure ID to filter by.
|
|
796
|
+
* @param {DateRange} [params.dateRange] - Optional date range to filter by (event start time).
|
|
797
|
+
* @param {CalendarEventStatus} [params.eventStatus] - Optional event status to filter by.
|
|
798
|
+
* @param {CalendarEventType} [params.eventType] - Optional event type to filter by.
|
|
799
|
+
* @returns {Promise<CalendarEvent[]>} A promise that resolves to an array of matching calendar events.
|
|
800
|
+
* @throws {Error} If the search location requires an entity ID that is not provided.
|
|
801
|
+
*/
|
|
802
|
+
async searchCalendarEvents(
|
|
803
|
+
params: SearchCalendarEventsParams
|
|
804
|
+
): Promise<CalendarEvent[]> {
|
|
805
|
+
// Use the utility function to perform the search
|
|
806
|
+
return searchCalendarEventsUtil(this.db, params);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Gets a doctor's upcoming appointments for a specific date range
|
|
811
|
+
*
|
|
812
|
+
* @param {string} doctorId - ID of the practitioner
|
|
813
|
+
* @param {Date} startDate - Start date of the range
|
|
814
|
+
* @param {Date} endDate - End date of the range
|
|
815
|
+
* @param {CalendarEventStatus} [status] - Optional status filter (defaults to CONFIRMED)
|
|
816
|
+
* @returns {Promise<CalendarEvent[]>} A promise that resolves to an array of appointments
|
|
817
|
+
*/
|
|
818
|
+
async getPractitionerUpcomingAppointments(
|
|
819
|
+
doctorId: string,
|
|
820
|
+
startDate: Date,
|
|
821
|
+
endDate: Date,
|
|
822
|
+
status: CalendarEventStatus = CalendarEventStatus.CONFIRMED
|
|
823
|
+
): Promise<CalendarEvent[]> {
|
|
824
|
+
// Create a date range for the query
|
|
825
|
+
const dateRange: DateRange = {
|
|
826
|
+
start: Timestamp.fromDate(startDate),
|
|
827
|
+
end: Timestamp.fromDate(endDate),
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
// Create the search parameters
|
|
831
|
+
const searchParams: SearchCalendarEventsParams = {
|
|
832
|
+
searchLocation: SearchLocationEnum.PRACTITIONER,
|
|
833
|
+
entityId: doctorId,
|
|
834
|
+
dateRange,
|
|
835
|
+
eventStatus: status,
|
|
836
|
+
eventType: CalendarEventType.APPOINTMENT,
|
|
837
|
+
};
|
|
838
|
+
|
|
839
|
+
// Search for the appointments
|
|
840
|
+
return this.searchCalendarEvents(searchParams);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Gets a patient's appointments for a specific date range
|
|
845
|
+
*
|
|
846
|
+
* @param {string} patientId - ID of the patient
|
|
847
|
+
* @param {Date} startDate - Start date of the range
|
|
848
|
+
* @param {Date} endDate - End date of the range
|
|
849
|
+
* @param {CalendarEventStatus} [status] - Optional status filter (defaults to all non-canceled appointments)
|
|
850
|
+
* @returns {Promise<CalendarEvent[]>} A promise that resolves to an array of appointments
|
|
851
|
+
*/
|
|
852
|
+
async getPatientAppointments(
|
|
853
|
+
patientId: string,
|
|
854
|
+
startDate: Date,
|
|
855
|
+
endDate: Date,
|
|
856
|
+
status?: CalendarEventStatus
|
|
857
|
+
): Promise<CalendarEvent[]> {
|
|
858
|
+
// Create a date range for the query
|
|
859
|
+
const dateRange: DateRange = {
|
|
860
|
+
start: Timestamp.fromDate(startDate),
|
|
861
|
+
end: Timestamp.fromDate(endDate),
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
// Create the search parameters
|
|
865
|
+
const searchParams: SearchCalendarEventsParams = {
|
|
866
|
+
searchLocation: SearchLocationEnum.PATIENT,
|
|
867
|
+
entityId: patientId,
|
|
868
|
+
dateRange,
|
|
869
|
+
eventType: CalendarEventType.APPOINTMENT,
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
// Add status filter if provided
|
|
873
|
+
if (status) {
|
|
874
|
+
searchParams.eventStatus = status;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Search for the appointments
|
|
878
|
+
return this.searchCalendarEvents(searchParams);
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Gets all appointments for a clinic within a specific date range
|
|
883
|
+
*
|
|
884
|
+
* @param {string} clinicId - ID of the clinic
|
|
885
|
+
* @param {Date} startDate - Start date of the range
|
|
886
|
+
* @param {Date} endDate - End date of the range
|
|
887
|
+
* @param {string} [doctorId] - Optional doctor ID to filter by
|
|
888
|
+
* @param {CalendarEventStatus} [status] - Optional status filter
|
|
889
|
+
* @returns {Promise<CalendarEvent[]>} A promise that resolves to an array of appointments
|
|
890
|
+
*/
|
|
891
|
+
async getClinicAppointments(
|
|
892
|
+
clinicId: string,
|
|
893
|
+
startDate: Date,
|
|
894
|
+
endDate: Date,
|
|
895
|
+
doctorId?: string,
|
|
896
|
+
status?: CalendarEventStatus
|
|
897
|
+
): Promise<CalendarEvent[]> {
|
|
898
|
+
// Create a date range for the query
|
|
899
|
+
const dateRange: DateRange = {
|
|
900
|
+
start: Timestamp.fromDate(startDate),
|
|
901
|
+
end: Timestamp.fromDate(endDate),
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
// Create the search parameters
|
|
905
|
+
const searchParams: SearchCalendarEventsParams = {
|
|
906
|
+
searchLocation: SearchLocationEnum.CLINIC,
|
|
907
|
+
entityId: clinicId,
|
|
908
|
+
dateRange,
|
|
909
|
+
eventType: CalendarEventType.APPOINTMENT,
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
// Add doctor filter if provided
|
|
913
|
+
if (doctorId) {
|
|
914
|
+
searchParams.practitionerId = doctorId;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// Add status filter if provided
|
|
918
|
+
if (status) {
|
|
919
|
+
searchParams.eventStatus = status;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Search for the appointments
|
|
923
|
+
return this.searchCalendarEvents(searchParams);
|
|
924
|
+
}
|
|
925
|
+
|
|
779
926
|
// #endregion
|
|
780
927
|
|
|
781
928
|
// #region Private Helper Methods
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
CreateCalendarEventData,
|
|
25
25
|
UpdateCalendarEventData,
|
|
26
26
|
CALENDAR_COLLECTION,
|
|
27
|
+
SearchCalendarEventsParams,
|
|
28
|
+
SearchLocationEnum,
|
|
27
29
|
} from "../../../types/calendar";
|
|
28
30
|
import { PRACTITIONERS_COLLECTION } from "../../../types/practitioner";
|
|
29
31
|
import { PATIENTS_COLLECTION } from "../../../types/patient";
|
|
@@ -508,3 +510,137 @@ export async function deleteClinicCalendarEventUtil(
|
|
|
508
510
|
);
|
|
509
511
|
await deleteDoc(eventRef);
|
|
510
512
|
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Searches for calendar events based on specified criteria
|
|
516
|
+
* @param db - Firestore instance
|
|
517
|
+
* @param params - Search parameters
|
|
518
|
+
* @param params.searchLocation - The primary location to search (practitioner, patient, or clinic)
|
|
519
|
+
* @param params.entityId - The ID of the entity (practitioner, patient, or clinic) to search within/for
|
|
520
|
+
* @param params.clinicId - Optional clinic ID to filter by
|
|
521
|
+
* @param params.practitionerId - Optional practitioner ID to filter by
|
|
522
|
+
* @param params.patientId - Optional patient ID to filter by
|
|
523
|
+
* @param params.procedureId - Optional procedure ID to filter by
|
|
524
|
+
* @param params.dateRange - Optional date range to filter by (event start time)
|
|
525
|
+
* @param params.eventStatus - Optional event status to filter by
|
|
526
|
+
* @param params.eventType - Optional event type to filter by
|
|
527
|
+
* @returns Promise resolving to an array of matching calendar events
|
|
528
|
+
*/
|
|
529
|
+
export async function searchCalendarEventsUtil(
|
|
530
|
+
db: Firestore,
|
|
531
|
+
params: SearchCalendarEventsParams
|
|
532
|
+
): Promise<CalendarEvent[]> {
|
|
533
|
+
const { searchLocation, entityId, ...filters } = params;
|
|
534
|
+
|
|
535
|
+
let baseCollectionPath: string;
|
|
536
|
+
const constraints: QueryConstraint[] = [];
|
|
537
|
+
|
|
538
|
+
// Determine the base collection and apply initial filter based on searchLocation
|
|
539
|
+
switch (searchLocation) {
|
|
540
|
+
case SearchLocationEnum.PRACTITIONER:
|
|
541
|
+
if (!entityId) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
"Practitioner ID (entityId) is required when searching practitioner calendar."
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
baseCollectionPath = `${PRACTITIONERS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
|
|
547
|
+
// If practitionerId filter is provided, it must match the entityId for this search location
|
|
548
|
+
if (filters.practitionerId && filters.practitionerId !== entityId) {
|
|
549
|
+
console.warn(
|
|
550
|
+
`Provided practitionerId filter (${filters.practitionerId}) does not match search entityId (${entityId}). Returning empty results.`
|
|
551
|
+
);
|
|
552
|
+
return [];
|
|
553
|
+
}
|
|
554
|
+
// Ensure we don't add a redundant filter if the caller also specified it
|
|
555
|
+
filters.practitionerId = undefined;
|
|
556
|
+
break;
|
|
557
|
+
|
|
558
|
+
case SearchLocationEnum.PATIENT:
|
|
559
|
+
if (!entityId) {
|
|
560
|
+
throw new Error(
|
|
561
|
+
"Patient ID (entityId) is required when searching patient calendar."
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
baseCollectionPath = `${PATIENTS_COLLECTION}/${entityId}/${CALENDAR_COLLECTION}`;
|
|
565
|
+
// If patientId filter is provided, it must match the entityId for this search location
|
|
566
|
+
if (filters.patientId && filters.patientId !== entityId) {
|
|
567
|
+
console.warn(
|
|
568
|
+
`Provided patientId filter (${filters.patientId}) does not match search entityId (${entityId}). Returning empty results.`
|
|
569
|
+
);
|
|
570
|
+
return [];
|
|
571
|
+
}
|
|
572
|
+
// Ensure we don't add a redundant filter if the caller also specified it
|
|
573
|
+
filters.patientId = undefined;
|
|
574
|
+
break;
|
|
575
|
+
|
|
576
|
+
case SearchLocationEnum.CLINIC:
|
|
577
|
+
if (!entityId) {
|
|
578
|
+
throw new Error(
|
|
579
|
+
"Clinic ID (entityId) is required when searching clinic-related events."
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
// Search the root CALENDAR_COLLECTION for events associated with the clinic
|
|
583
|
+
baseCollectionPath = CALENDAR_COLLECTION;
|
|
584
|
+
constraints.push(where("clinicBranchId", "==", entityId));
|
|
585
|
+
// If clinicId filter is provided, it must match the entityId for this search location
|
|
586
|
+
if (filters.clinicId && filters.clinicId !== entityId) {
|
|
587
|
+
console.warn(
|
|
588
|
+
`Provided clinicId filter (${filters.clinicId}) does not match search entityId (${entityId}). Returning empty results.`
|
|
589
|
+
);
|
|
590
|
+
return [];
|
|
591
|
+
}
|
|
592
|
+
// Ensure we don't add a redundant filter if the caller also specified it
|
|
593
|
+
filters.clinicId = undefined; // Already handled by the base query
|
|
594
|
+
break;
|
|
595
|
+
|
|
596
|
+
default:
|
|
597
|
+
throw new Error(`Invalid search location: ${searchLocation}`);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const collectionRef = collection(db, baseCollectionPath);
|
|
601
|
+
|
|
602
|
+
// Apply optional filters
|
|
603
|
+
if (filters.clinicId) {
|
|
604
|
+
constraints.push(where("clinicBranchId", "==", filters.clinicId));
|
|
605
|
+
}
|
|
606
|
+
if (filters.practitionerId) {
|
|
607
|
+
constraints.push(
|
|
608
|
+
where("practitionerProfileId", "==", filters.practitionerId)
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
if (filters.patientId) {
|
|
612
|
+
constraints.push(where("patientProfileId", "==", filters.patientId));
|
|
613
|
+
}
|
|
614
|
+
if (filters.procedureId) {
|
|
615
|
+
constraints.push(where("procedureId", "==", filters.procedureId));
|
|
616
|
+
}
|
|
617
|
+
if (filters.eventStatus) {
|
|
618
|
+
constraints.push(where("status", "==", filters.eventStatus));
|
|
619
|
+
}
|
|
620
|
+
if (filters.eventType) {
|
|
621
|
+
constraints.push(where("eventType", "==", filters.eventType));
|
|
622
|
+
}
|
|
623
|
+
if (filters.dateRange) {
|
|
624
|
+
// Firestore requires range filters on the same field
|
|
625
|
+
constraints.push(where("eventTime.start", ">=", filters.dateRange.start));
|
|
626
|
+
constraints.push(where("eventTime.start", "<=", filters.dateRange.end));
|
|
627
|
+
// Note: You might need to order by eventTime.start for range filters to work efficiently
|
|
628
|
+
// constraints.push(orderBy("eventTime.start"));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Build and execute the query
|
|
632
|
+
try {
|
|
633
|
+
const finalQuery = query(collectionRef, ...constraints);
|
|
634
|
+
const querySnapshot = await getDocs(finalQuery);
|
|
635
|
+
|
|
636
|
+
const events = querySnapshot.docs.map(
|
|
637
|
+
(doc) => ({ id: doc.id, ...doc.data() } as CalendarEvent)
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
return events;
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.error("Error searching calendar events:", error);
|
|
643
|
+
// Depending on requirements, you might want to return an empty array or re-throw
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// Export
|
|
1
|
+
// Export core calendar utilities
|
|
2
2
|
export * from "./calendar-event.utils";
|
|
3
|
-
|
|
4
|
-
// Export all synced calendar utilities
|
|
5
3
|
export * from "./synced-calendar.utils";
|
|
6
|
-
|
|
7
|
-
// Export all Google Calendar utilities
|
|
8
4
|
export * from "./google-calendar.utils";
|
|
5
|
+
export * from "./appointment.utils";
|
|
6
|
+
|
|
7
|
+
// Note: Other utility files (clinic.utils.ts, patient.utils.ts, practitioner.utils.ts, docs.utils.ts)
|
|
8
|
+
// should be imported directly where needed to avoid naming conflicts
|
|
@@ -478,6 +478,7 @@ export async function createAdminToken(
|
|
|
478
478
|
|
|
479
479
|
const now = Timestamp.now();
|
|
480
480
|
const expiresInDays = data?.expiresInDays || 7; // Default 7 days
|
|
481
|
+
const email = data?.email || null;
|
|
481
482
|
const expiresAt = new Timestamp(
|
|
482
483
|
now.seconds + expiresInDays * 24 * 60 * 60,
|
|
483
484
|
now.nanoseconds
|
|
@@ -487,11 +488,13 @@ export async function createAdminToken(
|
|
|
487
488
|
id: generateId(),
|
|
488
489
|
token: generateId(),
|
|
489
490
|
status: AdminTokenStatus.ACTIVE,
|
|
491
|
+
email,
|
|
490
492
|
createdAt: now,
|
|
491
493
|
expiresAt,
|
|
492
494
|
};
|
|
493
495
|
|
|
494
496
|
// Dodajemo token u grupu
|
|
497
|
+
// Ovo treba promeniti, staviti admin tokene u sub-kolekciju u klinickoj grupi
|
|
495
498
|
await updateClinicGroup(
|
|
496
499
|
db,
|
|
497
500
|
groupId,
|
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
UpdateMedicationData,
|
|
30
30
|
PatientDoctor,
|
|
31
31
|
PatientClinic,
|
|
32
|
+
SearchPatientsParams,
|
|
33
|
+
RequesterInfo,
|
|
32
34
|
} from "../../types/patient";
|
|
33
35
|
import { Auth } from "firebase/auth";
|
|
34
36
|
import { Firestore } from "firebase/firestore";
|
|
@@ -48,6 +50,7 @@ import {
|
|
|
48
50
|
deleteProfilePhotoUtil,
|
|
49
51
|
updatePatientProfileUtil,
|
|
50
52
|
updatePatientProfileByUserRefUtil,
|
|
53
|
+
searchPatientsUtil,
|
|
51
54
|
} from "./utils/profile.utils";
|
|
52
55
|
|
|
53
56
|
import {
|
|
@@ -468,4 +471,30 @@ export class PatientService extends BaseService {
|
|
|
468
471
|
): Promise<PatientProfile> {
|
|
469
472
|
return updatePatientProfileByUserRefUtil(this.db, userRef, data);
|
|
470
473
|
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Searches for patient profiles based on clinic/practitioner association.
|
|
477
|
+
* Requires information about the requester for security checks.
|
|
478
|
+
*
|
|
479
|
+
* @param {SearchPatientsParams} params - The search criteria (clinicId, practitionerId).
|
|
480
|
+
* @param {RequesterInfo} requester - Information about the user performing the search (ID, role, associated IDs).
|
|
481
|
+
* @returns {Promise<PatientProfile[]>} A promise resolving to an array of matching patient profiles.
|
|
482
|
+
*/
|
|
483
|
+
async searchPatients(
|
|
484
|
+
params: SearchPatientsParams,
|
|
485
|
+
requester: RequesterInfo
|
|
486
|
+
): Promise<PatientProfile[]> {
|
|
487
|
+
// We can potentially add more service-level logic here in the future,
|
|
488
|
+
// like fetching additional data or enriching the results.
|
|
489
|
+
// For now, we delegate directly to the utility function.
|
|
490
|
+
console.log(
|
|
491
|
+
`[PatientService.searchPatients] Initiating search with params:`,
|
|
492
|
+
params,
|
|
493
|
+
`by requester:`,
|
|
494
|
+
requester
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
// The utility function already handles validation and security checks.
|
|
498
|
+
return searchPatientsUtil(this.db, params, requester);
|
|
499
|
+
}
|
|
471
500
|
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
getDoc,
|
|
3
3
|
updateDoc,
|
|
4
4
|
arrayUnion,
|
|
5
|
+
arrayRemove,
|
|
5
6
|
Firestore,
|
|
6
7
|
serverTimestamp,
|
|
7
8
|
Timestamp,
|
|
@@ -27,10 +28,32 @@ export const addDoctorUtil = async (
|
|
|
27
28
|
isActive: true,
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
await
|
|
31
|
-
|
|
31
|
+
const patientDoc = await getDoc(getPatientDocRef(db, patientId));
|
|
32
|
+
if (!patientDoc.exists()) throw new Error("Patient profile not found");
|
|
33
|
+
const patientData = patientDoc.data() as PatientProfile;
|
|
34
|
+
const existingDoctorIndex = patientData.doctors?.findIndex(
|
|
35
|
+
(d) => d.userRef === doctorRef
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const updates: any = {
|
|
32
39
|
updatedAt: serverTimestamp(),
|
|
33
|
-
|
|
40
|
+
doctorIds: arrayUnion(doctorRef),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (existingDoctorIndex !== undefined && existingDoctorIndex > -1) {
|
|
44
|
+
const updatedDoctors = [...patientData.doctors];
|
|
45
|
+
updatedDoctors[existingDoctorIndex] = {
|
|
46
|
+
...updatedDoctors[existingDoctorIndex],
|
|
47
|
+
isActive: true,
|
|
48
|
+
assignedAt: Timestamp.now(),
|
|
49
|
+
assignedBy,
|
|
50
|
+
};
|
|
51
|
+
updates.doctors = updatedDoctors;
|
|
52
|
+
} else {
|
|
53
|
+
updates.doctors = arrayUnion(newDoctor);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await updateDoc(getPatientDocRef(db, patientId), updates);
|
|
34
57
|
};
|
|
35
58
|
|
|
36
59
|
export const removeDoctorUtil = async (
|
|
@@ -38,16 +61,19 @@ export const removeDoctorUtil = async (
|
|
|
38
61
|
patientId: string,
|
|
39
62
|
doctorRef: string
|
|
40
63
|
): Promise<void> => {
|
|
41
|
-
const
|
|
64
|
+
const patientDocRef = getPatientDocRef(db, patientId);
|
|
65
|
+
const patientDoc = await getDoc(patientDocRef);
|
|
42
66
|
if (!patientDoc.exists()) throw new Error("Patient profile not found");
|
|
43
67
|
|
|
44
68
|
const patientData = patientDoc.data() as PatientProfile;
|
|
45
|
-
const updatedDoctors = patientData.doctors.map((doctor) =>
|
|
46
|
-
doctor.userRef === doctorRef ? { ...doctor, isActive: false } : doctor
|
|
47
|
-
);
|
|
48
69
|
|
|
49
|
-
|
|
50
|
-
|
|
70
|
+
// Filter out the doctor object by userRef
|
|
71
|
+
const updatedDoctors =
|
|
72
|
+
patientData.doctors?.filter((doctor) => doctor.userRef !== doctorRef) || []; // Ensure it's an array even if doctors field didn't exist or was empty
|
|
73
|
+
|
|
74
|
+
await updateDoc(patientDocRef, {
|
|
75
|
+
doctors: updatedDoctors, // Set the filtered array
|
|
76
|
+
doctorIds: arrayRemove(doctorRef), // Remove ID from the denormalized list
|
|
51
77
|
updatedAt: serverTimestamp(),
|
|
52
78
|
});
|
|
53
79
|
};
|
|
@@ -66,10 +92,32 @@ export const addClinicUtil = async (
|
|
|
66
92
|
isActive: true,
|
|
67
93
|
};
|
|
68
94
|
|
|
69
|
-
await
|
|
70
|
-
|
|
95
|
+
const patientDoc = await getDoc(getPatientDocRef(db, patientId));
|
|
96
|
+
if (!patientDoc.exists()) throw new Error("Patient profile not found");
|
|
97
|
+
const patientData = patientDoc.data() as PatientProfile;
|
|
98
|
+
const existingClinicIndex = patientData.clinics?.findIndex(
|
|
99
|
+
(c) => c.clinicId === clinicId
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const updates: any = {
|
|
71
103
|
updatedAt: serverTimestamp(),
|
|
72
|
-
|
|
104
|
+
clinicIds: arrayUnion(clinicId),
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (existingClinicIndex !== undefined && existingClinicIndex > -1) {
|
|
108
|
+
const updatedClinics = [...patientData.clinics];
|
|
109
|
+
updatedClinics[existingClinicIndex] = {
|
|
110
|
+
...updatedClinics[existingClinicIndex],
|
|
111
|
+
isActive: true,
|
|
112
|
+
assignedAt: Timestamp.now(),
|
|
113
|
+
assignedBy,
|
|
114
|
+
};
|
|
115
|
+
updates.clinics = updatedClinics;
|
|
116
|
+
} else {
|
|
117
|
+
updates.clinics = arrayUnion(newClinic);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await updateDoc(getPatientDocRef(db, patientId), updates);
|
|
73
121
|
};
|
|
74
122
|
|
|
75
123
|
export const removeClinicUtil = async (
|
|
@@ -77,16 +125,19 @@ export const removeClinicUtil = async (
|
|
|
77
125
|
patientId: string,
|
|
78
126
|
clinicId: string
|
|
79
127
|
): Promise<void> => {
|
|
80
|
-
const
|
|
128
|
+
const patientDocRef = getPatientDocRef(db, patientId);
|
|
129
|
+
const patientDoc = await getDoc(patientDocRef);
|
|
81
130
|
if (!patientDoc.exists()) throw new Error("Patient profile not found");
|
|
82
131
|
|
|
83
132
|
const patientData = patientDoc.data() as PatientProfile;
|
|
84
|
-
const updatedClinics = patientData.clinics.map((clinic) =>
|
|
85
|
-
clinic.clinicId === clinicId ? { ...clinic, isActive: false } : clinic
|
|
86
|
-
);
|
|
87
133
|
|
|
88
|
-
|
|
89
|
-
|
|
134
|
+
// Filter out the clinic object by clinicId
|
|
135
|
+
const updatedClinics =
|
|
136
|
+
patientData.clinics?.filter((clinic) => clinic.clinicId !== clinicId) || []; // Ensure it's an array
|
|
137
|
+
|
|
138
|
+
await updateDoc(patientDocRef, {
|
|
139
|
+
clinics: updatedClinics, // Set the filtered array
|
|
140
|
+
clinicIds: arrayRemove(clinicId), // Remove ID from the denormalized list
|
|
90
141
|
updatedAt: serverTimestamp(),
|
|
91
142
|
});
|
|
92
143
|
};
|