@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
package/dist/admin/index.js
CHANGED
|
@@ -2600,23 +2600,81 @@ var CalendarAdminService = class {
|
|
|
2600
2600
|
);
|
|
2601
2601
|
const batch = this.db.batch();
|
|
2602
2602
|
const serverTimestamp = admin8.firestore.FieldValue.serverTimestamp();
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2603
|
+
let updatesAdded = 0;
|
|
2604
|
+
const calendarEventId = appointment.calendarEventId;
|
|
2605
|
+
if (!calendarEventId) {
|
|
2606
|
+
Logger.warn(
|
|
2607
|
+
`[CalendarAdminService] Missing calendar event ID for appointment ${appointment.id}`
|
|
2608
|
+
);
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
if (appointment.practitionerId) {
|
|
2612
|
+
const practitionerEventRef = this.db.doc(
|
|
2613
|
+
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
2614
|
+
);
|
|
2606
2615
|
batch.update(practitionerEventRef, {
|
|
2607
2616
|
status: newStatus,
|
|
2608
2617
|
updatedAt: serverTimestamp
|
|
2609
2618
|
});
|
|
2619
|
+
updatesAdded++;
|
|
2620
|
+
Logger.debug(
|
|
2621
|
+
`[CalendarAdminService] Added practitioner calendar event status update to batch`
|
|
2622
|
+
);
|
|
2623
|
+
} else {
|
|
2624
|
+
Logger.warn(
|
|
2625
|
+
`[CalendarAdminService] Missing practitioner ID for appointment ${appointment.id}`
|
|
2626
|
+
);
|
|
2610
2627
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
`[CalendarAdminService] Successfully updated calendar event statuses for appointment ${appointment.id}.`
|
|
2628
|
+
if (appointment.patientId) {
|
|
2629
|
+
const patientEventRef = this.db.doc(
|
|
2630
|
+
`${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
2615
2631
|
);
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2632
|
+
batch.update(patientEventRef, {
|
|
2633
|
+
status: newStatus,
|
|
2634
|
+
updatedAt: serverTimestamp
|
|
2635
|
+
});
|
|
2636
|
+
updatesAdded++;
|
|
2637
|
+
Logger.debug(
|
|
2638
|
+
`[CalendarAdminService] Added patient calendar event status update to batch`
|
|
2639
|
+
);
|
|
2640
|
+
} else {
|
|
2641
|
+
Logger.warn(
|
|
2642
|
+
`[CalendarAdminService] Missing patient ID for appointment ${appointment.id}`
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
if (appointment.clinicBranchId) {
|
|
2646
|
+
const clinicEventRef = this.db.doc(
|
|
2647
|
+
`${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
2648
|
+
);
|
|
2649
|
+
batch.update(clinicEventRef, {
|
|
2650
|
+
status: newStatus,
|
|
2651
|
+
updatedAt: serverTimestamp
|
|
2652
|
+
});
|
|
2653
|
+
updatesAdded++;
|
|
2654
|
+
Logger.debug(
|
|
2655
|
+
`[CalendarAdminService] Added clinic calendar event status update to batch`
|
|
2656
|
+
);
|
|
2657
|
+
} else {
|
|
2658
|
+
Logger.warn(
|
|
2659
|
+
`[CalendarAdminService] Missing clinic ID for appointment ${appointment.id}`
|
|
2660
|
+
);
|
|
2661
|
+
}
|
|
2662
|
+
if (updatesAdded > 0) {
|
|
2663
|
+
try {
|
|
2664
|
+
await batch.commit();
|
|
2665
|
+
Logger.info(
|
|
2666
|
+
`[CalendarAdminService] Successfully updated ${updatesAdded} calendar event statuses for appointment ${appointment.id}.`
|
|
2667
|
+
);
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
Logger.error(
|
|
2670
|
+
`[CalendarAdminService] Error updating calendar event statuses for appointment ${appointment.id}:`,
|
|
2671
|
+
error
|
|
2672
|
+
);
|
|
2673
|
+
throw error;
|
|
2674
|
+
}
|
|
2675
|
+
} else {
|
|
2676
|
+
Logger.warn(
|
|
2677
|
+
`[CalendarAdminService] No calendar event status updates were made for appointment ${appointment.id}`
|
|
2620
2678
|
);
|
|
2621
2679
|
}
|
|
2622
2680
|
}
|
|
@@ -2625,7 +2683,7 @@ var CalendarAdminService = class {
|
|
|
2625
2683
|
* associated with a given appointment.
|
|
2626
2684
|
*
|
|
2627
2685
|
* @param appointment - The appointment object.
|
|
2628
|
-
* @param newEventTime - The new CalendarEventTime object (using
|
|
2686
|
+
* @param newEventTime - The new CalendarEventTime object (using admin.firestore.Timestamp).
|
|
2629
2687
|
* @returns {Promise<void>} A promise that resolves when all updates are attempted.
|
|
2630
2688
|
*/
|
|
2631
2689
|
async updateAppointmentCalendarEventsTime(appointment, newEventTime) {
|
|
@@ -2634,28 +2692,85 @@ var CalendarAdminService = class {
|
|
|
2634
2692
|
);
|
|
2635
2693
|
const batch = this.db.batch();
|
|
2636
2694
|
const serverTimestamp = admin8.firestore.FieldValue.serverTimestamp();
|
|
2695
|
+
let updatesAdded = 0;
|
|
2696
|
+
const calendarEventId = appointment.calendarEventId;
|
|
2697
|
+
if (!calendarEventId) {
|
|
2698
|
+
Logger.warn(
|
|
2699
|
+
`[CalendarAdminService] Missing calendar event ID for appointment ${appointment.id}`
|
|
2700
|
+
);
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2637
2703
|
const firestoreCompatibleEventTime = {
|
|
2638
|
-
start:
|
|
2639
|
-
end:
|
|
2704
|
+
start: newEventTime.start,
|
|
2705
|
+
end: newEventTime.end
|
|
2640
2706
|
};
|
|
2641
|
-
if (appointment.
|
|
2707
|
+
if (appointment.practitionerId) {
|
|
2642
2708
|
const practitionerEventRef = this.db.doc(
|
|
2643
|
-
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${
|
|
2709
|
+
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
2644
2710
|
);
|
|
2645
2711
|
batch.update(practitionerEventRef, {
|
|
2646
2712
|
eventTime: firestoreCompatibleEventTime,
|
|
2647
2713
|
updatedAt: serverTimestamp
|
|
2648
2714
|
});
|
|
2715
|
+
updatesAdded++;
|
|
2716
|
+
Logger.debug(
|
|
2717
|
+
`[CalendarAdminService] Added practitioner calendar event time update to batch`
|
|
2718
|
+
);
|
|
2719
|
+
} else {
|
|
2720
|
+
Logger.warn(
|
|
2721
|
+
`[CalendarAdminService] Missing practitioner ID for appointment ${appointment.id}`
|
|
2722
|
+
);
|
|
2649
2723
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
`[CalendarAdminService] Successfully updated calendar event times for appointment ${appointment.id}.`
|
|
2724
|
+
if (appointment.patientId) {
|
|
2725
|
+
const patientEventRef = this.db.doc(
|
|
2726
|
+
`${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
2654
2727
|
);
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2728
|
+
batch.update(patientEventRef, {
|
|
2729
|
+
eventTime: firestoreCompatibleEventTime,
|
|
2730
|
+
updatedAt: serverTimestamp
|
|
2731
|
+
});
|
|
2732
|
+
updatesAdded++;
|
|
2733
|
+
Logger.debug(
|
|
2734
|
+
`[CalendarAdminService] Added patient calendar event time update to batch`
|
|
2735
|
+
);
|
|
2736
|
+
} else {
|
|
2737
|
+
Logger.warn(
|
|
2738
|
+
`[CalendarAdminService] Missing patient ID for appointment ${appointment.id}`
|
|
2739
|
+
);
|
|
2740
|
+
}
|
|
2741
|
+
if (appointment.clinicBranchId) {
|
|
2742
|
+
const clinicEventRef = this.db.doc(
|
|
2743
|
+
`${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
2744
|
+
);
|
|
2745
|
+
batch.update(clinicEventRef, {
|
|
2746
|
+
eventTime: firestoreCompatibleEventTime,
|
|
2747
|
+
updatedAt: serverTimestamp
|
|
2748
|
+
});
|
|
2749
|
+
updatesAdded++;
|
|
2750
|
+
Logger.debug(
|
|
2751
|
+
`[CalendarAdminService] Added clinic calendar event time update to batch`
|
|
2752
|
+
);
|
|
2753
|
+
} else {
|
|
2754
|
+
Logger.warn(
|
|
2755
|
+
`[CalendarAdminService] Missing clinic ID for appointment ${appointment.id}`
|
|
2756
|
+
);
|
|
2757
|
+
}
|
|
2758
|
+
if (updatesAdded > 0) {
|
|
2759
|
+
try {
|
|
2760
|
+
await batch.commit();
|
|
2761
|
+
Logger.info(
|
|
2762
|
+
`[CalendarAdminService] Successfully updated ${updatesAdded} calendar event times for appointment ${appointment.id}.`
|
|
2763
|
+
);
|
|
2764
|
+
} catch (error) {
|
|
2765
|
+
Logger.error(
|
|
2766
|
+
`[CalendarAdminService] Error updating calendar event times for appointment ${appointment.id}:`,
|
|
2767
|
+
error
|
|
2768
|
+
);
|
|
2769
|
+
throw error;
|
|
2770
|
+
}
|
|
2771
|
+
} else {
|
|
2772
|
+
Logger.warn(
|
|
2773
|
+
`[CalendarAdminService] No calendar event time updates were made for appointment ${appointment.id}`
|
|
2659
2774
|
);
|
|
2660
2775
|
}
|
|
2661
2776
|
}
|
|
@@ -2671,21 +2786,72 @@ var CalendarAdminService = class {
|
|
|
2671
2786
|
`[CalendarAdminService] Deleting calendar events for appointment ${appointment.id}`
|
|
2672
2787
|
);
|
|
2673
2788
|
const batch = this.db.batch();
|
|
2674
|
-
|
|
2789
|
+
let deletesAdded = 0;
|
|
2790
|
+
const calendarEventId = appointment.calendarEventId;
|
|
2791
|
+
if (!calendarEventId) {
|
|
2792
|
+
Logger.warn(
|
|
2793
|
+
`[CalendarAdminService] Missing calendar event ID for appointment ${appointment.id}`
|
|
2794
|
+
);
|
|
2795
|
+
return;
|
|
2796
|
+
}
|
|
2797
|
+
if (appointment.practitionerId) {
|
|
2675
2798
|
const practitionerEventRef = this.db.doc(
|
|
2676
|
-
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${
|
|
2799
|
+
`${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
2677
2800
|
);
|
|
2678
2801
|
batch.delete(practitionerEventRef);
|
|
2802
|
+
deletesAdded++;
|
|
2803
|
+
Logger.debug(
|
|
2804
|
+
`[CalendarAdminService] Added practitioner calendar event deletion to batch`
|
|
2805
|
+
);
|
|
2806
|
+
} else {
|
|
2807
|
+
Logger.warn(
|
|
2808
|
+
`[CalendarAdminService] Missing practitioner ID for appointment ${appointment.id}`
|
|
2809
|
+
);
|
|
2679
2810
|
}
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
`[CalendarAdminService] Successfully deleted calendar events for appointment ${appointment.id}.`
|
|
2811
|
+
if (appointment.patientId) {
|
|
2812
|
+
const patientEventRef = this.db.doc(
|
|
2813
|
+
`${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
2684
2814
|
);
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2815
|
+
batch.delete(patientEventRef);
|
|
2816
|
+
deletesAdded++;
|
|
2817
|
+
Logger.debug(
|
|
2818
|
+
`[CalendarAdminService] Added patient calendar event deletion to batch`
|
|
2819
|
+
);
|
|
2820
|
+
} else {
|
|
2821
|
+
Logger.warn(
|
|
2822
|
+
`[CalendarAdminService] Missing patient ID for appointment ${appointment.id}`
|
|
2823
|
+
);
|
|
2824
|
+
}
|
|
2825
|
+
if (appointment.clinicBranchId) {
|
|
2826
|
+
const clinicEventRef = this.db.doc(
|
|
2827
|
+
`${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
|
|
2828
|
+
);
|
|
2829
|
+
batch.delete(clinicEventRef);
|
|
2830
|
+
deletesAdded++;
|
|
2831
|
+
Logger.debug(
|
|
2832
|
+
`[CalendarAdminService] Added clinic calendar event deletion to batch`
|
|
2833
|
+
);
|
|
2834
|
+
} else {
|
|
2835
|
+
Logger.warn(
|
|
2836
|
+
`[CalendarAdminService] Missing clinic ID for appointment ${appointment.id}`
|
|
2837
|
+
);
|
|
2838
|
+
}
|
|
2839
|
+
if (deletesAdded > 0) {
|
|
2840
|
+
try {
|
|
2841
|
+
await batch.commit();
|
|
2842
|
+
Logger.info(
|
|
2843
|
+
`[CalendarAdminService] Successfully deleted ${deletesAdded} calendar events for appointment ${appointment.id}.`
|
|
2844
|
+
);
|
|
2845
|
+
} catch (error) {
|
|
2846
|
+
Logger.error(
|
|
2847
|
+
`[CalendarAdminService] Error deleting calendar events for appointment ${appointment.id}:`,
|
|
2848
|
+
error
|
|
2849
|
+
);
|
|
2850
|
+
throw error;
|
|
2851
|
+
}
|
|
2852
|
+
} else {
|
|
2853
|
+
Logger.warn(
|
|
2854
|
+
`[CalendarAdminService] No calendar event deletions were made for appointment ${appointment.id}`
|
|
2689
2855
|
);
|
|
2690
2856
|
}
|
|
2691
2857
|
}
|
|
@@ -3022,7 +3188,6 @@ var AppointmentAggregationService = class {
|
|
|
3022
3188
|
`[AggService] Handling CREATE for appointment: ${appointment.id}, patient: ${appointment.patientId}, status: ${appointment.status}`
|
|
3023
3189
|
);
|
|
3024
3190
|
try {
|
|
3025
|
-
await this.managePatientClinicPractitionerLinks(appointment, "create");
|
|
3026
3191
|
const [
|
|
3027
3192
|
patientProfile,
|
|
3028
3193
|
patientSensitiveInfo,
|
|
@@ -3036,6 +3201,14 @@ var AppointmentAggregationService = class {
|
|
|
3036
3201
|
this.fetchClinicInfo(appointment.clinicBranchId)
|
|
3037
3202
|
// Needed for clinic admin notifications
|
|
3038
3203
|
]);
|
|
3204
|
+
if (patientProfile) {
|
|
3205
|
+
await this.managePatientClinicPractitionerLinks(
|
|
3206
|
+
patientProfile,
|
|
3207
|
+
appointment.practitionerId,
|
|
3208
|
+
appointment.clinicBranchId,
|
|
3209
|
+
"create"
|
|
3210
|
+
);
|
|
3211
|
+
}
|
|
3039
3212
|
if (appointment.status === "confirmed" /* CONFIRMED */) {
|
|
3040
3213
|
Logger.info(
|
|
3041
3214
|
`[AggService] Appt ${appointment.id} created as CONFIRMED.`
|
|
@@ -3125,7 +3298,7 @@ var AppointmentAggregationService = class {
|
|
|
3125
3298
|
* @returns {Promise<void>}
|
|
3126
3299
|
*/
|
|
3127
3300
|
async handleAppointmentUpdate(before, after) {
|
|
3128
|
-
var _a, _b;
|
|
3301
|
+
var _a, _b, _c;
|
|
3129
3302
|
Logger.info(
|
|
3130
3303
|
`[AggService] Handling UPDATE for appointment: ${after.id}. Status ${before.status} -> ${after.status}`
|
|
3131
3304
|
);
|
|
@@ -3150,6 +3323,10 @@ var AppointmentAggregationService = class {
|
|
|
3150
3323
|
if (before.status === "pending" /* PENDING */ && after.status === "confirmed" /* CONFIRMED */) {
|
|
3151
3324
|
Logger.info(`[AggService] Appt ${after.id} PENDING -> CONFIRMED.`);
|
|
3152
3325
|
await this.createPreAppointmentRequirementInstances(after);
|
|
3326
|
+
await this.calendarAdminService.updateAppointmentCalendarEventsStatus(
|
|
3327
|
+
after,
|
|
3328
|
+
"confirmed" /* CONFIRMED */
|
|
3329
|
+
);
|
|
3153
3330
|
if ((patientSensitiveInfo == null ? void 0 : patientSensitiveInfo.email) && patientProfile) {
|
|
3154
3331
|
Logger.info(
|
|
3155
3332
|
`[AggService] Sending appointment confirmed email to patient ${patientSensitiveInfo.email}`
|
|
@@ -3189,7 +3366,53 @@ var AppointmentAggregationService = class {
|
|
|
3189
3366
|
};
|
|
3190
3367
|
await this.appointmentMailingService.sendAppointmentConfirmedEmail(
|
|
3191
3368
|
practitionerEmailData
|
|
3192
|
-
|
|
3369
|
+
);
|
|
3370
|
+
}
|
|
3371
|
+
} else if (before.status === "rescheduled_by_clinic" /* RESCHEDULED_BY_CLINIC */ && after.status === "confirmed" /* CONFIRMED */) {
|
|
3372
|
+
Logger.info(
|
|
3373
|
+
`[AggService] Appt ${after.id} RESCHEDULED_BY_CLINIC -> CONFIRMED.`
|
|
3374
|
+
);
|
|
3375
|
+
await this.updateRelatedPatientRequirementInstances(
|
|
3376
|
+
before,
|
|
3377
|
+
"supersededReschedule" /* SUPERSEDED_RESCHEDULE */
|
|
3378
|
+
);
|
|
3379
|
+
await this.createPreAppointmentRequirementInstances(after);
|
|
3380
|
+
await this.calendarAdminService.updateAppointmentCalendarEventsStatus(
|
|
3381
|
+
after,
|
|
3382
|
+
"confirmed" /* CONFIRMED */
|
|
3383
|
+
);
|
|
3384
|
+
if ((patientSensitiveInfo == null ? void 0 : patientSensitiveInfo.email) && patientProfile) {
|
|
3385
|
+
Logger.info(
|
|
3386
|
+
`[AggService] Sending appointment confirmed email to patient ${patientSensitiveInfo.email}`
|
|
3387
|
+
);
|
|
3388
|
+
const emailData = {
|
|
3389
|
+
appointment: after,
|
|
3390
|
+
recipientProfile: after.patientInfo,
|
|
3391
|
+
recipientRole: "patient"
|
|
3392
|
+
};
|
|
3393
|
+
await this.appointmentMailingService.sendAppointmentConfirmedEmail(
|
|
3394
|
+
emailData
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
if ((patientProfile == null ? void 0 : patientProfile.expoTokens) && patientProfile.expoTokens.length > 0) {
|
|
3398
|
+
await this.notificationsAdmin.sendAppointmentConfirmedPush(
|
|
3399
|
+
after,
|
|
3400
|
+
after.patientId,
|
|
3401
|
+
patientProfile.expoTokens,
|
|
3402
|
+
"patient" /* PATIENT */
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
3405
|
+
if ((_b = practitionerProfile == null ? void 0 : practitionerProfile.basicInfo) == null ? void 0 : _b.email) {
|
|
3406
|
+
Logger.info(
|
|
3407
|
+
`[AggService] Sending appointment confirmation email to practitioner ${practitionerProfile.basicInfo.email}`
|
|
3408
|
+
);
|
|
3409
|
+
const practitionerEmailData = {
|
|
3410
|
+
appointment: after,
|
|
3411
|
+
recipientProfile: after.practitionerInfo,
|
|
3412
|
+
recipientRole: "practitioner"
|
|
3413
|
+
};
|
|
3414
|
+
await this.appointmentMailingService.sendAppointmentConfirmedEmail(
|
|
3415
|
+
practitionerEmailData
|
|
3193
3416
|
);
|
|
3194
3417
|
}
|
|
3195
3418
|
} else if (after.status === "canceled_clinic" /* CANCELED_CLINIC */ || after.status === "canceled_patient" /* CANCELED_PATIENT */ || after.status === "canceled_patient_rescheduled" /* CANCELED_PATIENT_RESCHEDULED */ || after.status === "no_show" /* NO_SHOW */) {
|
|
@@ -3200,7 +3423,33 @@ var AppointmentAggregationService = class {
|
|
|
3200
3423
|
after,
|
|
3201
3424
|
"cancelledAppointment" /* CANCELLED_APPOINTMENT */
|
|
3202
3425
|
);
|
|
3203
|
-
|
|
3426
|
+
if (patientProfile) {
|
|
3427
|
+
await this.managePatientClinicPractitionerLinks(
|
|
3428
|
+
patientProfile,
|
|
3429
|
+
after.practitionerId,
|
|
3430
|
+
after.clinicBranchId,
|
|
3431
|
+
"cancel",
|
|
3432
|
+
after.status
|
|
3433
|
+
);
|
|
3434
|
+
}
|
|
3435
|
+
const calendarStatus = (status) => {
|
|
3436
|
+
switch (status) {
|
|
3437
|
+
case "no_show" /* NO_SHOW */:
|
|
3438
|
+
return "no_show" /* NO_SHOW */;
|
|
3439
|
+
case "canceled_clinic" /* CANCELED_CLINIC */:
|
|
3440
|
+
return "rejected" /* REJECTED */;
|
|
3441
|
+
case "canceled_patient" /* CANCELED_PATIENT */:
|
|
3442
|
+
return "canceled" /* CANCELED */;
|
|
3443
|
+
case "canceled_patient_rescheduled" /* CANCELED_PATIENT_RESCHEDULED */:
|
|
3444
|
+
return "rejected" /* REJECTED */;
|
|
3445
|
+
default:
|
|
3446
|
+
return "canceled" /* CANCELED */;
|
|
3447
|
+
}
|
|
3448
|
+
};
|
|
3449
|
+
await this.calendarAdminService.updateAppointmentCalendarEventsStatus(
|
|
3450
|
+
after,
|
|
3451
|
+
calendarStatus(after.status)
|
|
3452
|
+
);
|
|
3204
3453
|
if ((patientSensitiveInfo == null ? void 0 : patientSensitiveInfo.email) && patientProfile) {
|
|
3205
3454
|
Logger.info(
|
|
3206
3455
|
`[AggService] Sending appointment cancellation email to patient ${patientSensitiveInfo.email}`
|
|
@@ -3216,7 +3465,7 @@ var AppointmentAggregationService = class {
|
|
|
3216
3465
|
// TODO: Properly import types
|
|
3217
3466
|
);
|
|
3218
3467
|
}
|
|
3219
|
-
if ((
|
|
3468
|
+
if ((_c = practitionerProfile == null ? void 0 : practitionerProfile.basicInfo) == null ? void 0 : _c.email) {
|
|
3220
3469
|
Logger.info(
|
|
3221
3470
|
`[AggService] Sending appointment cancellation email to practitioner ${practitionerProfile.basicInfo.email}`
|
|
3222
3471
|
);
|
|
@@ -3234,6 +3483,10 @@ var AppointmentAggregationService = class {
|
|
|
3234
3483
|
} else if (after.status === "completed" /* COMPLETED */) {
|
|
3235
3484
|
Logger.info(`[AggService] Appt ${after.id} status -> COMPLETED.`);
|
|
3236
3485
|
await this.createPostAppointmentRequirementInstances(after);
|
|
3486
|
+
await this.calendarAdminService.updateAppointmentCalendarEventsStatus(
|
|
3487
|
+
after,
|
|
3488
|
+
"completed" /* COMPLETED */
|
|
3489
|
+
);
|
|
3237
3490
|
if ((patientSensitiveInfo == null ? void 0 : patientSensitiveInfo.email) && patientProfile) {
|
|
3238
3491
|
Logger.info(
|
|
3239
3492
|
`[AggService] Sending review request email to patient ${patientSensitiveInfo.email}`
|
|
@@ -3258,8 +3511,16 @@ var AppointmentAggregationService = class {
|
|
|
3258
3511
|
// Pass the 'before' state for old requirements
|
|
3259
3512
|
"supersededReschedule" /* SUPERSEDED_RESCHEDULE */
|
|
3260
3513
|
);
|
|
3261
|
-
|
|
3262
|
-
|
|
3514
|
+
await this.calendarAdminService.updateAppointmentCalendarEventsTime(
|
|
3515
|
+
after,
|
|
3516
|
+
{
|
|
3517
|
+
start: after.appointmentStartTime,
|
|
3518
|
+
end: after.appointmentEndTime
|
|
3519
|
+
}
|
|
3520
|
+
);
|
|
3521
|
+
await this.calendarAdminService.updateAppointmentCalendarEventsStatus(
|
|
3522
|
+
after,
|
|
3523
|
+
"pending" /* PENDING */
|
|
3263
3524
|
);
|
|
3264
3525
|
if ((patientSensitiveInfo == null ? void 0 : patientSensitiveInfo.email) && patientProfile) {
|
|
3265
3526
|
Logger.info(
|
|
@@ -3284,9 +3545,24 @@ var AppointmentAggregationService = class {
|
|
|
3284
3545
|
}
|
|
3285
3546
|
if (timeChanged && !statusChanged) {
|
|
3286
3547
|
Logger.info(`[AggService] Appointment ${after.id} time changed.`);
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3548
|
+
if (after.status === "confirmed" /* CONFIRMED */) {
|
|
3549
|
+
await this.updateRelatedPatientRequirementInstances(
|
|
3550
|
+
before,
|
|
3551
|
+
"supersededReschedule" /* SUPERSEDED_RESCHEDULE */
|
|
3552
|
+
);
|
|
3553
|
+
await this.createPreAppointmentRequirementInstances(after);
|
|
3554
|
+
await this.calendarAdminService.updateAppointmentCalendarEventsTime(
|
|
3555
|
+
after,
|
|
3556
|
+
{
|
|
3557
|
+
start: after.appointmentStartTime,
|
|
3558
|
+
end: after.appointmentEndTime
|
|
3559
|
+
}
|
|
3560
|
+
);
|
|
3561
|
+
} else {
|
|
3562
|
+
Logger.warn(
|
|
3563
|
+
`[AggService] Independent time change detected for ${after.id} with status ${after.status}. Review implications for requirements and calendar.`
|
|
3564
|
+
);
|
|
3565
|
+
}
|
|
3290
3566
|
}
|
|
3291
3567
|
Logger.info(
|
|
3292
3568
|
`[AggService] Successfully processed UPDATE for appointment: ${after.id}`
|
|
@@ -3311,9 +3587,19 @@ var AppointmentAggregationService = class {
|
|
|
3311
3587
|
deletedAppointment,
|
|
3312
3588
|
"cancelledAppointment" /* CANCELLED_APPOINTMENT */
|
|
3313
3589
|
);
|
|
3314
|
-
await this.
|
|
3315
|
-
deletedAppointment
|
|
3316
|
-
|
|
3590
|
+
const patientProfile = await this.fetchPatientProfile(
|
|
3591
|
+
deletedAppointment.patientId
|
|
3592
|
+
);
|
|
3593
|
+
if (patientProfile) {
|
|
3594
|
+
await this.managePatientClinicPractitionerLinks(
|
|
3595
|
+
patientProfile,
|
|
3596
|
+
deletedAppointment.practitionerId,
|
|
3597
|
+
deletedAppointment.clinicBranchId,
|
|
3598
|
+
"cancel"
|
|
3599
|
+
);
|
|
3600
|
+
}
|
|
3601
|
+
await this.calendarAdminService.deleteAppointmentCalendarEvents(
|
|
3602
|
+
deletedAppointment
|
|
3317
3603
|
);
|
|
3318
3604
|
}
|
|
3319
3605
|
// --- Helper Methods for Aggregation Logic ---
|
|
@@ -3594,18 +3880,47 @@ var AppointmentAggregationService = class {
|
|
|
3594
3880
|
try {
|
|
3595
3881
|
const batch = this.db.batch();
|
|
3596
3882
|
let instancesCreatedCount = 0;
|
|
3883
|
+
let createdInstances = [];
|
|
3884
|
+
Logger.info(
|
|
3885
|
+
`[AggService] Found ${appointment.postProcedureRequirements.length} post-requirements to process: ${JSON.stringify(
|
|
3886
|
+
appointment.postProcedureRequirements.map((r) => {
|
|
3887
|
+
var _a2, _b;
|
|
3888
|
+
return {
|
|
3889
|
+
id: r.id,
|
|
3890
|
+
name: r.name,
|
|
3891
|
+
type: r.type,
|
|
3892
|
+
isActive: r.isActive,
|
|
3893
|
+
hasTimeframe: !!r.timeframe,
|
|
3894
|
+
notifyAtLength: ((_b = (_a2 = r.timeframe) == null ? void 0 : _a2.notifyAt) == null ? void 0 : _b.length) || 0
|
|
3895
|
+
};
|
|
3896
|
+
})
|
|
3897
|
+
)}`
|
|
3898
|
+
);
|
|
3597
3899
|
for (const template of appointment.postProcedureRequirements) {
|
|
3598
|
-
if (!template)
|
|
3900
|
+
if (!template) {
|
|
3901
|
+
Logger.warn(
|
|
3902
|
+
`[AggService] Found null/undefined template in postProcedureRequirements array`
|
|
3903
|
+
);
|
|
3904
|
+
continue;
|
|
3905
|
+
}
|
|
3599
3906
|
if (template.type !== "post" /* POST */ || !template.isActive) {
|
|
3600
3907
|
Logger.debug(
|
|
3601
3908
|
`[AggService] Skipping template ${template.id} (${template.name}): not an active POST requirement.`
|
|
3602
3909
|
);
|
|
3603
3910
|
continue;
|
|
3604
3911
|
}
|
|
3912
|
+
if (!template.timeframe || !template.timeframe.notifyAt || template.timeframe.notifyAt.length === 0) {
|
|
3913
|
+
Logger.warn(
|
|
3914
|
+
`[AggService] Template ${template.id} (${template.name}) has no timeframe.notifyAt values. Creating with empty instructions.`
|
|
3915
|
+
);
|
|
3916
|
+
}
|
|
3605
3917
|
Logger.debug(
|
|
3606
|
-
`[AggService] Processing
|
|
3918
|
+
`[AggService] Processing template ${template.id} (${template.name}) for appt ${appointment.id}`
|
|
3607
3919
|
);
|
|
3608
3920
|
const newInstanceRef = this.db.collection(PATIENTS_COLLECTION).doc(appointment.patientId).collection(PATIENT_REQUIREMENTS_SUBCOLLECTION_NAME).doc();
|
|
3921
|
+
Logger.debug(
|
|
3922
|
+
`[AggService] Created doc reference: ${newInstanceRef.path}`
|
|
3923
|
+
);
|
|
3609
3924
|
const instructions = (((_a = template.timeframe) == null ? void 0 : _a.notifyAt) || []).map((notifyAtValue) => {
|
|
3610
3925
|
let dueTime = appointment.appointmentEndTime;
|
|
3611
3926
|
if (template.timeframe && typeof notifyAtValue === "number") {
|
|
@@ -3620,7 +3935,7 @@ var AppointmentAggregationService = class {
|
|
|
3620
3935
|
dueTime = admin10.firestore.Timestamp.fromDate(dueDateTime);
|
|
3621
3936
|
}
|
|
3622
3937
|
const actionableWindowHours = template.importance === "high" ? 1 : template.importance === "medium" ? 4 : 15;
|
|
3623
|
-
|
|
3938
|
+
const instructionObject = {
|
|
3624
3939
|
instructionId: `${template.id}_${notifyAtValue}_${newInstanceRef.id}`.replace(
|
|
3625
3940
|
/[^a-zA-Z0-9_]/g,
|
|
3626
3941
|
"_"
|
|
@@ -3632,36 +3947,142 @@ var AppointmentAggregationService = class {
|
|
|
3632
3947
|
originalNotifyAtValue: notifyAtValue,
|
|
3633
3948
|
originalTimeframeUnit: template.timeframe.unit,
|
|
3634
3949
|
updatedAt: admin10.firestore.Timestamp.now(),
|
|
3635
|
-
// Use current server timestamp
|
|
3636
3950
|
notificationId: void 0,
|
|
3637
3951
|
actionTakenAt: void 0
|
|
3638
3952
|
};
|
|
3953
|
+
return instructionObject;
|
|
3639
3954
|
});
|
|
3640
3955
|
const newInstanceData = {
|
|
3956
|
+
id: newInstanceRef.id,
|
|
3641
3957
|
patientId: appointment.patientId,
|
|
3642
3958
|
appointmentId: appointment.id,
|
|
3643
3959
|
originalRequirementId: template.id,
|
|
3644
3960
|
requirementName: template.name,
|
|
3645
3961
|
requirementDescription: template.description,
|
|
3646
3962
|
requirementType: template.type,
|
|
3647
|
-
// Should be RequirementType.POST
|
|
3648
3963
|
requirementImportance: template.importance,
|
|
3649
3964
|
overallStatus: "active" /* ACTIVE */,
|
|
3650
3965
|
instructions,
|
|
3651
3966
|
createdAt: admin10.firestore.FieldValue.serverTimestamp(),
|
|
3652
3967
|
updatedAt: admin10.firestore.FieldValue.serverTimestamp()
|
|
3653
3968
|
};
|
|
3969
|
+
Logger.debug(
|
|
3970
|
+
`[AggService] Setting data for requirement: ${JSON.stringify({
|
|
3971
|
+
id: newInstanceRef.id,
|
|
3972
|
+
patientId: newInstanceData.patientId,
|
|
3973
|
+
appointmentId: newInstanceData.appointmentId,
|
|
3974
|
+
requirementName: newInstanceData.requirementName,
|
|
3975
|
+
instructionsCount: newInstanceData.instructions.length
|
|
3976
|
+
})}`
|
|
3977
|
+
);
|
|
3654
3978
|
batch.set(newInstanceRef, newInstanceData);
|
|
3979
|
+
createdInstances.push({
|
|
3980
|
+
ref: newInstanceRef,
|
|
3981
|
+
data: newInstanceData
|
|
3982
|
+
});
|
|
3655
3983
|
instancesCreatedCount++;
|
|
3656
3984
|
Logger.debug(
|
|
3657
|
-
`[AggService] Added
|
|
3985
|
+
`[AggService] Added PatientRequirementInstance ${newInstanceRef.id} to batch for template ${template.id}.`
|
|
3658
3986
|
);
|
|
3659
3987
|
}
|
|
3660
3988
|
if (instancesCreatedCount > 0) {
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3989
|
+
try {
|
|
3990
|
+
await batch.commit();
|
|
3991
|
+
Logger.info(
|
|
3992
|
+
`[AggService] Successfully created ${instancesCreatedCount} POST_APPOINTMENT requirement instances for appointment ${appointment.id}.`
|
|
3993
|
+
);
|
|
3994
|
+
try {
|
|
3995
|
+
const verifySnapshot = await this.db.collection(PATIENTS_COLLECTION).doc(appointment.patientId).collection(PATIENT_REQUIREMENTS_SUBCOLLECTION_NAME).where("appointmentId", "==", appointment.id).get();
|
|
3996
|
+
if (verifySnapshot.empty) {
|
|
3997
|
+
Logger.warn(
|
|
3998
|
+
`[AggService] Batch commit reported success but documents not found! Attempting direct creation as fallback...`
|
|
3999
|
+
);
|
|
4000
|
+
const fallbackPromises = createdInstances.map(
|
|
4001
|
+
async ({ ref, data }) => {
|
|
4002
|
+
try {
|
|
4003
|
+
await ref.set(data);
|
|
4004
|
+
Logger.info(
|
|
4005
|
+
`[AggService] Fallback direct creation success for ${ref.id}`
|
|
4006
|
+
);
|
|
4007
|
+
return true;
|
|
4008
|
+
} catch (fallbackError) {
|
|
4009
|
+
Logger.error(
|
|
4010
|
+
`[AggService] Fallback direct creation failed for ${ref.id}:`,
|
|
4011
|
+
fallbackError
|
|
4012
|
+
);
|
|
4013
|
+
return false;
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
);
|
|
4017
|
+
const fallbackResults = await Promise.allSettled(
|
|
4018
|
+
fallbackPromises
|
|
4019
|
+
);
|
|
4020
|
+
const successCount = fallbackResults.filter(
|
|
4021
|
+
(r) => r.status === "fulfilled" && r.value === true
|
|
4022
|
+
).length;
|
|
4023
|
+
if (successCount > 0) {
|
|
4024
|
+
Logger.info(
|
|
4025
|
+
`[AggService] Fallback mechanism created ${successCount} out of ${createdInstances.length} requirements`
|
|
4026
|
+
);
|
|
4027
|
+
} else {
|
|
4028
|
+
Logger.error(
|
|
4029
|
+
`[AggService] Both batch and fallback mechanisms failed to create requirements`
|
|
4030
|
+
);
|
|
4031
|
+
throw new Error(
|
|
4032
|
+
"Failed to create patient requirements through both batch and direct methods"
|
|
4033
|
+
);
|
|
4034
|
+
}
|
|
4035
|
+
} else {
|
|
4036
|
+
Logger.info(
|
|
4037
|
+
`[AggService] Verification confirmed ${verifySnapshot.size} requirement documents created`
|
|
4038
|
+
);
|
|
4039
|
+
}
|
|
4040
|
+
} catch (verifyError) {
|
|
4041
|
+
Logger.error(
|
|
4042
|
+
`[AggService] Error during verification of created requirements:`,
|
|
4043
|
+
verifyError
|
|
4044
|
+
);
|
|
4045
|
+
}
|
|
4046
|
+
} catch (commitError) {
|
|
4047
|
+
Logger.error(
|
|
4048
|
+
`[AggService] Error committing batch for POST_APPOINTMENT requirement instances for appointment ${appointment.id}:`,
|
|
4049
|
+
commitError
|
|
4050
|
+
);
|
|
4051
|
+
Logger.info(`[AggService] Attempting direct creation as fallback...`);
|
|
4052
|
+
const fallbackPromises = createdInstances.map(
|
|
4053
|
+
async ({ ref, data }) => {
|
|
4054
|
+
try {
|
|
4055
|
+
await ref.set(data);
|
|
4056
|
+
Logger.info(
|
|
4057
|
+
`[AggService] Fallback direct creation success for ${ref.id}`
|
|
4058
|
+
);
|
|
4059
|
+
return true;
|
|
4060
|
+
} catch (fallbackError) {
|
|
4061
|
+
Logger.error(
|
|
4062
|
+
`[AggService] Fallback direct creation failed for ${ref.id}:`,
|
|
4063
|
+
fallbackError
|
|
4064
|
+
);
|
|
4065
|
+
return false;
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
);
|
|
4069
|
+
const fallbackResults = await Promise.allSettled(fallbackPromises);
|
|
4070
|
+
const successCount = fallbackResults.filter(
|
|
4071
|
+
(r) => r.status === "fulfilled" && r.value === true
|
|
4072
|
+
).length;
|
|
4073
|
+
if (successCount > 0) {
|
|
4074
|
+
Logger.info(
|
|
4075
|
+
`[AggService] Fallback mechanism created ${successCount} out of ${createdInstances.length} requirements`
|
|
4076
|
+
);
|
|
4077
|
+
} else {
|
|
4078
|
+
Logger.error(
|
|
4079
|
+
`[AggService] Both batch and fallback mechanisms failed to create requirements`
|
|
4080
|
+
);
|
|
4081
|
+
throw new Error(
|
|
4082
|
+
"Failed to create patient requirements through both batch and direct methods"
|
|
4083
|
+
);
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
3665
4086
|
} else {
|
|
3666
4087
|
Logger.info(
|
|
3667
4088
|
`[AggService] No new POST_APPOINTMENT requirement instances were prepared for batch commit for appointment ${appointment.id}.`
|
|
@@ -3672,6 +4093,7 @@ var AppointmentAggregationService = class {
|
|
|
3672
4093
|
`[AggService] Error creating POST_APPOINTMENT requirement instances for appointment ${appointment.id}:`,
|
|
3673
4094
|
error
|
|
3674
4095
|
);
|
|
4096
|
+
throw error;
|
|
3675
4097
|
}
|
|
3676
4098
|
}
|
|
3677
4099
|
/**
|
|
@@ -3737,75 +4159,165 @@ var AppointmentAggregationService = class {
|
|
|
3737
4159
|
}
|
|
3738
4160
|
}
|
|
3739
4161
|
/**
|
|
3740
|
-
* Manages
|
|
3741
|
-
*
|
|
3742
|
-
* Removes patientId on appointment cancellation (basic removal, can be enhanced).
|
|
4162
|
+
* Manages relationships between a patient and clinics/practitioners.
|
|
4163
|
+
* Only updates the patient profile with doctorIds and clinicIds.
|
|
3743
4164
|
*
|
|
3744
|
-
* @param {
|
|
3745
|
-
* @param {
|
|
4165
|
+
* @param {PatientProfile} patientProfile - The patient profile to update
|
|
4166
|
+
* @param {string} practitionerId - The practitioner ID
|
|
4167
|
+
* @param {string} clinicId - The clinic ID
|
|
4168
|
+
* @param {"create" | "cancel"} action - 'create' to add IDs, 'cancel' to potentially remove them
|
|
4169
|
+
* @param {AppointmentStatus} [cancelStatus] - The appointment status if action is 'cancel'
|
|
3746
4170
|
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
3747
4171
|
*/
|
|
3748
|
-
async managePatientClinicPractitionerLinks(
|
|
4172
|
+
async managePatientClinicPractitionerLinks(patientProfile, practitionerId, clinicId, action, cancelStatus) {
|
|
3749
4173
|
Logger.info(
|
|
3750
|
-
`[AggService] Managing patient-clinic-practitioner links for
|
|
4174
|
+
`[AggService] Managing patient-clinic-practitioner links for patient ${patientProfile.id}, action: ${action}`
|
|
3751
4175
|
);
|
|
3752
|
-
|
|
4176
|
+
try {
|
|
4177
|
+
if (action === "create") {
|
|
4178
|
+
await this.addPatientLinks(patientProfile, practitionerId, clinicId);
|
|
4179
|
+
} else if (action === "cancel") {
|
|
4180
|
+
await this.removePatientLinksIfNoActiveAppointments(
|
|
4181
|
+
patientProfile,
|
|
4182
|
+
practitionerId,
|
|
4183
|
+
clinicId
|
|
4184
|
+
);
|
|
4185
|
+
}
|
|
4186
|
+
} catch (error) {
|
|
3753
4187
|
Logger.error(
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
patientId: appointment.patientId,
|
|
3757
|
-
practitionerId: appointment.practitionerId,
|
|
3758
|
-
clinicBranchId: appointment.clinicBranchId
|
|
3759
|
-
}
|
|
4188
|
+
`[AggService] Error managing patient-clinic-practitioner links for patient ${patientProfile.id}:`,
|
|
4189
|
+
error
|
|
3760
4190
|
);
|
|
3761
|
-
return;
|
|
3762
4191
|
}
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
);
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
4192
|
+
}
|
|
4193
|
+
/**
|
|
4194
|
+
* Adds practitioner and clinic IDs to the patient profile.
|
|
4195
|
+
*
|
|
4196
|
+
* @param {PatientProfile} patientProfile - The patient profile to update
|
|
4197
|
+
* @param {string} practitionerId - The practitioner ID to add
|
|
4198
|
+
* @param {string} clinicId - The clinic ID to add
|
|
4199
|
+
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
4200
|
+
*/
|
|
4201
|
+
async addPatientLinks(patientProfile, practitionerId, clinicId) {
|
|
4202
|
+
var _a, _b;
|
|
4203
|
+
try {
|
|
4204
|
+
const hasDoctor = ((_a = patientProfile.doctorIds) == null ? void 0 : _a.includes(practitionerId)) || false;
|
|
4205
|
+
const hasClinic = ((_b = patientProfile.clinicIds) == null ? void 0 : _b.includes(clinicId)) || false;
|
|
4206
|
+
if (!hasDoctor || !hasClinic) {
|
|
4207
|
+
const patientRef = this.db.collection(PATIENTS_COLLECTION).doc(patientProfile.id);
|
|
4208
|
+
const updateData = {
|
|
4209
|
+
updatedAt: admin10.firestore.FieldValue.serverTimestamp()
|
|
4210
|
+
};
|
|
4211
|
+
if (!hasDoctor) {
|
|
4212
|
+
Logger.debug(
|
|
4213
|
+
`[AggService] Adding practitioner ${practitionerId} to patient ${patientProfile.id}`
|
|
4214
|
+
);
|
|
4215
|
+
updateData.doctorIds = admin10.firestore.FieldValue.arrayUnion(practitionerId);
|
|
4216
|
+
}
|
|
4217
|
+
if (!hasClinic) {
|
|
4218
|
+
Logger.debug(
|
|
4219
|
+
`[AggService] Adding clinic ${clinicId} to patient ${patientProfile.id}`
|
|
4220
|
+
);
|
|
4221
|
+
updateData.clinicIds = admin10.firestore.FieldValue.arrayUnion(clinicId);
|
|
4222
|
+
}
|
|
4223
|
+
await patientRef.update(updateData);
|
|
4224
|
+
Logger.info(
|
|
4225
|
+
`[AggService] Successfully updated patient ${patientProfile.id} with new links.`
|
|
4226
|
+
);
|
|
4227
|
+
} else {
|
|
4228
|
+
Logger.info(
|
|
4229
|
+
`[AggService] Patient ${patientProfile.id} already has links to both practitioner ${practitionerId} and clinic ${clinicId}.`
|
|
4230
|
+
);
|
|
4231
|
+
}
|
|
4232
|
+
} catch (error) {
|
|
4233
|
+
Logger.error(
|
|
4234
|
+
`[AggService] Error updating patient ${patientProfile.id} with new links:`,
|
|
4235
|
+
error
|
|
3797
4236
|
);
|
|
4237
|
+
throw error;
|
|
3798
4238
|
}
|
|
4239
|
+
}
|
|
4240
|
+
/**
|
|
4241
|
+
* Removes practitioner and clinic IDs from the patient profile if there are no more active appointments.
|
|
4242
|
+
*
|
|
4243
|
+
* @param {PatientProfile} patientProfile - The patient profile to update
|
|
4244
|
+
* @param {string} practitionerId - The practitioner ID to remove
|
|
4245
|
+
* @param {string} clinicId - The clinic ID to remove
|
|
4246
|
+
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
|
4247
|
+
*/
|
|
4248
|
+
async removePatientLinksIfNoActiveAppointments(patientProfile, practitionerId, clinicId) {
|
|
4249
|
+
var _a, _b;
|
|
3799
4250
|
try {
|
|
3800
|
-
await
|
|
4251
|
+
const activePractitionerAppointments = await this.checkActiveAppointments(
|
|
4252
|
+
patientProfile.id,
|
|
4253
|
+
"practitionerId",
|
|
4254
|
+
practitionerId
|
|
4255
|
+
);
|
|
4256
|
+
const activeClinicAppointments = await this.checkActiveAppointments(
|
|
4257
|
+
patientProfile.id,
|
|
4258
|
+
"clinicBranchId",
|
|
4259
|
+
clinicId
|
|
4260
|
+
);
|
|
3801
4261
|
Logger.info(
|
|
3802
|
-
`[AggService]
|
|
4262
|
+
`[AggService] Active appointment count for patient ${patientProfile.id}: With practitioner ${practitionerId}: ${activePractitionerAppointments}, With clinic ${clinicId}: ${activeClinicAppointments}`
|
|
3803
4263
|
);
|
|
4264
|
+
const patientRef = this.db.collection(PATIENTS_COLLECTION).doc(patientProfile.id);
|
|
4265
|
+
const updateData = {};
|
|
4266
|
+
let updateNeeded = false;
|
|
4267
|
+
if (activePractitionerAppointments === 0 && ((_a = patientProfile.doctorIds) == null ? void 0 : _a.includes(practitionerId))) {
|
|
4268
|
+
Logger.debug(
|
|
4269
|
+
`[AggService] Removing practitioner ${practitionerId} from patient ${patientProfile.id}`
|
|
4270
|
+
);
|
|
4271
|
+
updateData.doctorIds = admin10.firestore.FieldValue.arrayRemove(practitionerId);
|
|
4272
|
+
updateNeeded = true;
|
|
4273
|
+
}
|
|
4274
|
+
if (activeClinicAppointments === 0 && ((_b = patientProfile.clinicIds) == null ? void 0 : _b.includes(clinicId))) {
|
|
4275
|
+
Logger.debug(
|
|
4276
|
+
`[AggService] Removing clinic ${clinicId} from patient ${patientProfile.id}`
|
|
4277
|
+
);
|
|
4278
|
+
updateData.clinicIds = admin10.firestore.FieldValue.arrayRemove(clinicId);
|
|
4279
|
+
updateNeeded = true;
|
|
4280
|
+
}
|
|
4281
|
+
if (updateNeeded) {
|
|
4282
|
+
updateData.updatedAt = admin10.firestore.FieldValue.serverTimestamp();
|
|
4283
|
+
await patientRef.update(updateData);
|
|
4284
|
+
Logger.info(
|
|
4285
|
+
`[AggService] Successfully removed links from patient ${patientProfile.id}`
|
|
4286
|
+
);
|
|
4287
|
+
} else {
|
|
4288
|
+
Logger.info(
|
|
4289
|
+
`[AggService] No links need to be removed from patient ${patientProfile.id}`
|
|
4290
|
+
);
|
|
4291
|
+
}
|
|
3804
4292
|
} catch (error) {
|
|
3805
4293
|
Logger.error(
|
|
3806
|
-
`[AggService] Error
|
|
4294
|
+
`[AggService] Error removing links from patient profile:`,
|
|
3807
4295
|
error
|
|
3808
4296
|
);
|
|
4297
|
+
throw error;
|
|
4298
|
+
}
|
|
4299
|
+
}
|
|
4300
|
+
/**
|
|
4301
|
+
* Checks if there are active appointments between a patient and another entity (practitioner or clinic).
|
|
4302
|
+
*
|
|
4303
|
+
* @param {string} patientId - The patient ID.
|
|
4304
|
+
* @param {"practitionerId" | "clinicBranchId"} entityField - The field to check for the entity ID.
|
|
4305
|
+
* @param {string} entityId - The entity ID (practitioner or clinic).
|
|
4306
|
+
* @returns {Promise<number>} The number of active appointments found.
|
|
4307
|
+
*/
|
|
4308
|
+
async checkActiveAppointments(patientId, entityField, entityId) {
|
|
4309
|
+
try {
|
|
4310
|
+
const inactiveStatuses = [
|
|
4311
|
+
"canceled_clinic" /* CANCELED_CLINIC */,
|
|
4312
|
+
"canceled_patient" /* CANCELED_PATIENT */,
|
|
4313
|
+
"canceled_patient_rescheduled" /* CANCELED_PATIENT_RESCHEDULED */,
|
|
4314
|
+
"no_show" /* NO_SHOW */
|
|
4315
|
+
];
|
|
4316
|
+
const snapshot = await this.db.collection("appointments").where("patientId", "==", patientId).where(entityField, "==", entityId).where("status", "not-in", inactiveStatuses).get();
|
|
4317
|
+
return snapshot.size;
|
|
4318
|
+
} catch (error) {
|
|
4319
|
+
Logger.error(`[AggService] Error checking active appointments:`, error);
|
|
4320
|
+
throw error;
|
|
3809
4321
|
}
|
|
3810
4322
|
}
|
|
3811
4323
|
// --- Data Fetching Helpers (Consider moving to a data access layer or using existing services if available) ---
|