@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.
@@ -2545,23 +2545,81 @@ var CalendarAdminService = class {
2545
2545
  );
2546
2546
  const batch = this.db.batch();
2547
2547
  const serverTimestamp = admin8.firestore.FieldValue.serverTimestamp();
2548
- const practitionerCalendarEventPath = `${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${appointment.calendarEventId}`;
2549
- if (appointment.calendarEventId) {
2550
- const practitionerEventRef = this.db.doc(practitionerCalendarEventPath);
2548
+ let updatesAdded = 0;
2549
+ const calendarEventId = appointment.calendarEventId;
2550
+ if (!calendarEventId) {
2551
+ Logger.warn(
2552
+ `[CalendarAdminService] Missing calendar event ID for appointment ${appointment.id}`
2553
+ );
2554
+ return;
2555
+ }
2556
+ if (appointment.practitionerId) {
2557
+ const practitionerEventRef = this.db.doc(
2558
+ `${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
2559
+ );
2551
2560
  batch.update(practitionerEventRef, {
2552
2561
  status: newStatus,
2553
2562
  updatedAt: serverTimestamp
2554
2563
  });
2564
+ updatesAdded++;
2565
+ Logger.debug(
2566
+ `[CalendarAdminService] Added practitioner calendar event status update to batch`
2567
+ );
2568
+ } else {
2569
+ Logger.warn(
2570
+ `[CalendarAdminService] Missing practitioner ID for appointment ${appointment.id}`
2571
+ );
2555
2572
  }
2556
- try {
2557
- await batch.commit();
2558
- Logger.info(
2559
- `[CalendarAdminService] Successfully updated calendar event statuses for appointment ${appointment.id}.`
2573
+ if (appointment.patientId) {
2574
+ const patientEventRef = this.db.doc(
2575
+ `${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
2560
2576
  );
2561
- } catch (error) {
2562
- Logger.error(
2563
- `[CalendarAdminService] Error updating calendar event statuses for appointment ${appointment.id}:`,
2564
- error
2577
+ batch.update(patientEventRef, {
2578
+ status: newStatus,
2579
+ updatedAt: serverTimestamp
2580
+ });
2581
+ updatesAdded++;
2582
+ Logger.debug(
2583
+ `[CalendarAdminService] Added patient calendar event status update to batch`
2584
+ );
2585
+ } else {
2586
+ Logger.warn(
2587
+ `[CalendarAdminService] Missing patient ID for appointment ${appointment.id}`
2588
+ );
2589
+ }
2590
+ if (appointment.clinicBranchId) {
2591
+ const clinicEventRef = this.db.doc(
2592
+ `${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
2593
+ );
2594
+ batch.update(clinicEventRef, {
2595
+ status: newStatus,
2596
+ updatedAt: serverTimestamp
2597
+ });
2598
+ updatesAdded++;
2599
+ Logger.debug(
2600
+ `[CalendarAdminService] Added clinic calendar event status update to batch`
2601
+ );
2602
+ } else {
2603
+ Logger.warn(
2604
+ `[CalendarAdminService] Missing clinic ID for appointment ${appointment.id}`
2605
+ );
2606
+ }
2607
+ if (updatesAdded > 0) {
2608
+ try {
2609
+ await batch.commit();
2610
+ Logger.info(
2611
+ `[CalendarAdminService] Successfully updated ${updatesAdded} calendar event statuses for appointment ${appointment.id}.`
2612
+ );
2613
+ } catch (error) {
2614
+ Logger.error(
2615
+ `[CalendarAdminService] Error updating calendar event statuses for appointment ${appointment.id}:`,
2616
+ error
2617
+ );
2618
+ throw error;
2619
+ }
2620
+ } else {
2621
+ Logger.warn(
2622
+ `[CalendarAdminService] No calendar event status updates were made for appointment ${appointment.id}`
2565
2623
  );
2566
2624
  }
2567
2625
  }
@@ -2570,7 +2628,7 @@ var CalendarAdminService = class {
2570
2628
  * associated with a given appointment.
2571
2629
  *
2572
2630
  * @param appointment - The appointment object.
2573
- * @param newEventTime - The new CalendarEventTime object (using FirebaseClientTimestamp).
2631
+ * @param newEventTime - The new CalendarEventTime object (using admin.firestore.Timestamp).
2574
2632
  * @returns {Promise<void>} A promise that resolves when all updates are attempted.
2575
2633
  */
2576
2634
  async updateAppointmentCalendarEventsTime(appointment, newEventTime) {
@@ -2579,28 +2637,85 @@ var CalendarAdminService = class {
2579
2637
  );
2580
2638
  const batch = this.db.batch();
2581
2639
  const serverTimestamp = admin8.firestore.FieldValue.serverTimestamp();
2640
+ let updatesAdded = 0;
2641
+ const calendarEventId = appointment.calendarEventId;
2642
+ if (!calendarEventId) {
2643
+ Logger.warn(
2644
+ `[CalendarAdminService] Missing calendar event ID for appointment ${appointment.id}`
2645
+ );
2646
+ return;
2647
+ }
2582
2648
  const firestoreCompatibleEventTime = {
2583
- start: TimestampUtils.clientToAdmin(newEventTime.start) || admin8.firestore.Timestamp.now(),
2584
- end: TimestampUtils.clientToAdmin(newEventTime.end) || admin8.firestore.Timestamp.now()
2649
+ start: newEventTime.start,
2650
+ end: newEventTime.end
2585
2651
  };
2586
- if (appointment.calendarEventId) {
2652
+ if (appointment.practitionerId) {
2587
2653
  const practitionerEventRef = this.db.doc(
2588
- `${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${appointment.calendarEventId}`
2654
+ `${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
2589
2655
  );
2590
2656
  batch.update(practitionerEventRef, {
2591
2657
  eventTime: firestoreCompatibleEventTime,
2592
2658
  updatedAt: serverTimestamp
2593
2659
  });
2660
+ updatesAdded++;
2661
+ Logger.debug(
2662
+ `[CalendarAdminService] Added practitioner calendar event time update to batch`
2663
+ );
2664
+ } else {
2665
+ Logger.warn(
2666
+ `[CalendarAdminService] Missing practitioner ID for appointment ${appointment.id}`
2667
+ );
2594
2668
  }
2595
- try {
2596
- await batch.commit();
2597
- Logger.info(
2598
- `[CalendarAdminService] Successfully updated calendar event times for appointment ${appointment.id}.`
2669
+ if (appointment.patientId) {
2670
+ const patientEventRef = this.db.doc(
2671
+ `${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
2599
2672
  );
2600
- } catch (error) {
2601
- Logger.error(
2602
- `[CalendarAdminService] Error updating calendar event times for appointment ${appointment.id}:`,
2603
- error
2673
+ batch.update(patientEventRef, {
2674
+ eventTime: firestoreCompatibleEventTime,
2675
+ updatedAt: serverTimestamp
2676
+ });
2677
+ updatesAdded++;
2678
+ Logger.debug(
2679
+ `[CalendarAdminService] Added patient calendar event time update to batch`
2680
+ );
2681
+ } else {
2682
+ Logger.warn(
2683
+ `[CalendarAdminService] Missing patient ID for appointment ${appointment.id}`
2684
+ );
2685
+ }
2686
+ if (appointment.clinicBranchId) {
2687
+ const clinicEventRef = this.db.doc(
2688
+ `${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
2689
+ );
2690
+ batch.update(clinicEventRef, {
2691
+ eventTime: firestoreCompatibleEventTime,
2692
+ updatedAt: serverTimestamp
2693
+ });
2694
+ updatesAdded++;
2695
+ Logger.debug(
2696
+ `[CalendarAdminService] Added clinic calendar event time update to batch`
2697
+ );
2698
+ } else {
2699
+ Logger.warn(
2700
+ `[CalendarAdminService] Missing clinic ID for appointment ${appointment.id}`
2701
+ );
2702
+ }
2703
+ if (updatesAdded > 0) {
2704
+ try {
2705
+ await batch.commit();
2706
+ Logger.info(
2707
+ `[CalendarAdminService] Successfully updated ${updatesAdded} calendar event times for appointment ${appointment.id}.`
2708
+ );
2709
+ } catch (error) {
2710
+ Logger.error(
2711
+ `[CalendarAdminService] Error updating calendar event times for appointment ${appointment.id}:`,
2712
+ error
2713
+ );
2714
+ throw error;
2715
+ }
2716
+ } else {
2717
+ Logger.warn(
2718
+ `[CalendarAdminService] No calendar event time updates were made for appointment ${appointment.id}`
2604
2719
  );
2605
2720
  }
2606
2721
  }
@@ -2616,21 +2731,72 @@ var CalendarAdminService = class {
2616
2731
  `[CalendarAdminService] Deleting calendar events for appointment ${appointment.id}`
2617
2732
  );
2618
2733
  const batch = this.db.batch();
2619
- if (appointment.calendarEventId) {
2734
+ let deletesAdded = 0;
2735
+ const calendarEventId = appointment.calendarEventId;
2736
+ if (!calendarEventId) {
2737
+ Logger.warn(
2738
+ `[CalendarAdminService] Missing calendar event ID for appointment ${appointment.id}`
2739
+ );
2740
+ return;
2741
+ }
2742
+ if (appointment.practitionerId) {
2620
2743
  const practitionerEventRef = this.db.doc(
2621
- `${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${appointment.calendarEventId}`
2744
+ `${PRACTITIONERS_COLLECTION}/${appointment.practitionerId}/${CALENDAR_COLLECTION}/${calendarEventId}`
2622
2745
  );
2623
2746
  batch.delete(practitionerEventRef);
2747
+ deletesAdded++;
2748
+ Logger.debug(
2749
+ `[CalendarAdminService] Added practitioner calendar event deletion to batch`
2750
+ );
2751
+ } else {
2752
+ Logger.warn(
2753
+ `[CalendarAdminService] Missing practitioner ID for appointment ${appointment.id}`
2754
+ );
2624
2755
  }
2625
- try {
2626
- await batch.commit();
2627
- Logger.info(
2628
- `[CalendarAdminService] Successfully deleted calendar events for appointment ${appointment.id}.`
2756
+ if (appointment.patientId) {
2757
+ const patientEventRef = this.db.doc(
2758
+ `${PATIENTS_COLLECTION}/${appointment.patientId}/${CALENDAR_COLLECTION}/${calendarEventId}`
2629
2759
  );
2630
- } catch (error) {
2631
- Logger.error(
2632
- `[CalendarAdminService] Error deleting calendar events for appointment ${appointment.id}:`,
2633
- error
2760
+ batch.delete(patientEventRef);
2761
+ deletesAdded++;
2762
+ Logger.debug(
2763
+ `[CalendarAdminService] Added patient calendar event deletion to batch`
2764
+ );
2765
+ } else {
2766
+ Logger.warn(
2767
+ `[CalendarAdminService] Missing patient ID for appointment ${appointment.id}`
2768
+ );
2769
+ }
2770
+ if (appointment.clinicBranchId) {
2771
+ const clinicEventRef = this.db.doc(
2772
+ `${CLINICS_COLLECTION}/${appointment.clinicBranchId}/${CALENDAR_COLLECTION}/${calendarEventId}`
2773
+ );
2774
+ batch.delete(clinicEventRef);
2775
+ deletesAdded++;
2776
+ Logger.debug(
2777
+ `[CalendarAdminService] Added clinic calendar event deletion to batch`
2778
+ );
2779
+ } else {
2780
+ Logger.warn(
2781
+ `[CalendarAdminService] Missing clinic ID for appointment ${appointment.id}`
2782
+ );
2783
+ }
2784
+ if (deletesAdded > 0) {
2785
+ try {
2786
+ await batch.commit();
2787
+ Logger.info(
2788
+ `[CalendarAdminService] Successfully deleted ${deletesAdded} calendar events for appointment ${appointment.id}.`
2789
+ );
2790
+ } catch (error) {
2791
+ Logger.error(
2792
+ `[CalendarAdminService] Error deleting calendar events for appointment ${appointment.id}:`,
2793
+ error
2794
+ );
2795
+ throw error;
2796
+ }
2797
+ } else {
2798
+ Logger.warn(
2799
+ `[CalendarAdminService] No calendar event deletions were made for appointment ${appointment.id}`
2634
2800
  );
2635
2801
  }
2636
2802
  }
@@ -2967,7 +3133,6 @@ var AppointmentAggregationService = class {
2967
3133
  `[AggService] Handling CREATE for appointment: ${appointment.id}, patient: ${appointment.patientId}, status: ${appointment.status}`
2968
3134
  );
2969
3135
  try {
2970
- await this.managePatientClinicPractitionerLinks(appointment, "create");
2971
3136
  const [
2972
3137
  patientProfile,
2973
3138
  patientSensitiveInfo,
@@ -2981,6 +3146,14 @@ var AppointmentAggregationService = class {
2981
3146
  this.fetchClinicInfo(appointment.clinicBranchId)
2982
3147
  // Needed for clinic admin notifications
2983
3148
  ]);
3149
+ if (patientProfile) {
3150
+ await this.managePatientClinicPractitionerLinks(
3151
+ patientProfile,
3152
+ appointment.practitionerId,
3153
+ appointment.clinicBranchId,
3154
+ "create"
3155
+ );
3156
+ }
2984
3157
  if (appointment.status === "confirmed" /* CONFIRMED */) {
2985
3158
  Logger.info(
2986
3159
  `[AggService] Appt ${appointment.id} created as CONFIRMED.`
@@ -3070,7 +3243,7 @@ var AppointmentAggregationService = class {
3070
3243
  * @returns {Promise<void>}
3071
3244
  */
3072
3245
  async handleAppointmentUpdate(before, after) {
3073
- var _a, _b;
3246
+ var _a, _b, _c;
3074
3247
  Logger.info(
3075
3248
  `[AggService] Handling UPDATE for appointment: ${after.id}. Status ${before.status} -> ${after.status}`
3076
3249
  );
@@ -3095,6 +3268,10 @@ var AppointmentAggregationService = class {
3095
3268
  if (before.status === "pending" /* PENDING */ && after.status === "confirmed" /* CONFIRMED */) {
3096
3269
  Logger.info(`[AggService] Appt ${after.id} PENDING -> CONFIRMED.`);
3097
3270
  await this.createPreAppointmentRequirementInstances(after);
3271
+ await this.calendarAdminService.updateAppointmentCalendarEventsStatus(
3272
+ after,
3273
+ "confirmed" /* CONFIRMED */
3274
+ );
3098
3275
  if ((patientSensitiveInfo == null ? void 0 : patientSensitiveInfo.email) && patientProfile) {
3099
3276
  Logger.info(
3100
3277
  `[AggService] Sending appointment confirmed email to patient ${patientSensitiveInfo.email}`
@@ -3134,7 +3311,53 @@ var AppointmentAggregationService = class {
3134
3311
  };
3135
3312
  await this.appointmentMailingService.sendAppointmentConfirmedEmail(
3136
3313
  practitionerEmailData
3137
- // TODO: Properly import PractitionerProfileInfo and ensure type compatibility
3314
+ );
3315
+ }
3316
+ } else if (before.status === "rescheduled_by_clinic" /* RESCHEDULED_BY_CLINIC */ && after.status === "confirmed" /* CONFIRMED */) {
3317
+ Logger.info(
3318
+ `[AggService] Appt ${after.id} RESCHEDULED_BY_CLINIC -> CONFIRMED.`
3319
+ );
3320
+ await this.updateRelatedPatientRequirementInstances(
3321
+ before,
3322
+ "supersededReschedule" /* SUPERSEDED_RESCHEDULE */
3323
+ );
3324
+ await this.createPreAppointmentRequirementInstances(after);
3325
+ await this.calendarAdminService.updateAppointmentCalendarEventsStatus(
3326
+ after,
3327
+ "confirmed" /* CONFIRMED */
3328
+ );
3329
+ if ((patientSensitiveInfo == null ? void 0 : patientSensitiveInfo.email) && patientProfile) {
3330
+ Logger.info(
3331
+ `[AggService] Sending appointment confirmed email to patient ${patientSensitiveInfo.email}`
3332
+ );
3333
+ const emailData = {
3334
+ appointment: after,
3335
+ recipientProfile: after.patientInfo,
3336
+ recipientRole: "patient"
3337
+ };
3338
+ await this.appointmentMailingService.sendAppointmentConfirmedEmail(
3339
+ emailData
3340
+ );
3341
+ }
3342
+ if ((patientProfile == null ? void 0 : patientProfile.expoTokens) && patientProfile.expoTokens.length > 0) {
3343
+ await this.notificationsAdmin.sendAppointmentConfirmedPush(
3344
+ after,
3345
+ after.patientId,
3346
+ patientProfile.expoTokens,
3347
+ "patient" /* PATIENT */
3348
+ );
3349
+ }
3350
+ if ((_b = practitionerProfile == null ? void 0 : practitionerProfile.basicInfo) == null ? void 0 : _b.email) {
3351
+ Logger.info(
3352
+ `[AggService] Sending appointment confirmation email to practitioner ${practitionerProfile.basicInfo.email}`
3353
+ );
3354
+ const practitionerEmailData = {
3355
+ appointment: after,
3356
+ recipientProfile: after.practitionerInfo,
3357
+ recipientRole: "practitioner"
3358
+ };
3359
+ await this.appointmentMailingService.sendAppointmentConfirmedEmail(
3360
+ practitionerEmailData
3138
3361
  );
3139
3362
  }
3140
3363
  } 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 */) {
@@ -3145,7 +3368,33 @@ var AppointmentAggregationService = class {
3145
3368
  after,
3146
3369
  "cancelledAppointment" /* CANCELLED_APPOINTMENT */
3147
3370
  );
3148
- await this.managePatientClinicPractitionerLinks(after, "cancel");
3371
+ if (patientProfile) {
3372
+ await this.managePatientClinicPractitionerLinks(
3373
+ patientProfile,
3374
+ after.practitionerId,
3375
+ after.clinicBranchId,
3376
+ "cancel",
3377
+ after.status
3378
+ );
3379
+ }
3380
+ const calendarStatus = (status) => {
3381
+ switch (status) {
3382
+ case "no_show" /* NO_SHOW */:
3383
+ return "no_show" /* NO_SHOW */;
3384
+ case "canceled_clinic" /* CANCELED_CLINIC */:
3385
+ return "rejected" /* REJECTED */;
3386
+ case "canceled_patient" /* CANCELED_PATIENT */:
3387
+ return "canceled" /* CANCELED */;
3388
+ case "canceled_patient_rescheduled" /* CANCELED_PATIENT_RESCHEDULED */:
3389
+ return "rejected" /* REJECTED */;
3390
+ default:
3391
+ return "canceled" /* CANCELED */;
3392
+ }
3393
+ };
3394
+ await this.calendarAdminService.updateAppointmentCalendarEventsStatus(
3395
+ after,
3396
+ calendarStatus(after.status)
3397
+ );
3149
3398
  if ((patientSensitiveInfo == null ? void 0 : patientSensitiveInfo.email) && patientProfile) {
3150
3399
  Logger.info(
3151
3400
  `[AggService] Sending appointment cancellation email to patient ${patientSensitiveInfo.email}`
@@ -3161,7 +3410,7 @@ var AppointmentAggregationService = class {
3161
3410
  // TODO: Properly import types
3162
3411
  );
3163
3412
  }
3164
- if ((_b = practitionerProfile == null ? void 0 : practitionerProfile.basicInfo) == null ? void 0 : _b.email) {
3413
+ if ((_c = practitionerProfile == null ? void 0 : practitionerProfile.basicInfo) == null ? void 0 : _c.email) {
3165
3414
  Logger.info(
3166
3415
  `[AggService] Sending appointment cancellation email to practitioner ${practitionerProfile.basicInfo.email}`
3167
3416
  );
@@ -3179,6 +3428,10 @@ var AppointmentAggregationService = class {
3179
3428
  } else if (after.status === "completed" /* COMPLETED */) {
3180
3429
  Logger.info(`[AggService] Appt ${after.id} status -> COMPLETED.`);
3181
3430
  await this.createPostAppointmentRequirementInstances(after);
3431
+ await this.calendarAdminService.updateAppointmentCalendarEventsStatus(
3432
+ after,
3433
+ "completed" /* COMPLETED */
3434
+ );
3182
3435
  if ((patientSensitiveInfo == null ? void 0 : patientSensitiveInfo.email) && patientProfile) {
3183
3436
  Logger.info(
3184
3437
  `[AggService] Sending review request email to patient ${patientSensitiveInfo.email}`
@@ -3203,8 +3456,16 @@ var AppointmentAggregationService = class {
3203
3456
  // Pass the 'before' state for old requirements
3204
3457
  "supersededReschedule" /* SUPERSEDED_RESCHEDULE */
3205
3458
  );
3206
- Logger.info(
3207
- `[AggService] TODO: Handle RESCHEDULE logic for requirements carefully based on confirmation flow.`
3459
+ await this.calendarAdminService.updateAppointmentCalendarEventsTime(
3460
+ after,
3461
+ {
3462
+ start: after.appointmentStartTime,
3463
+ end: after.appointmentEndTime
3464
+ }
3465
+ );
3466
+ await this.calendarAdminService.updateAppointmentCalendarEventsStatus(
3467
+ after,
3468
+ "pending" /* PENDING */
3208
3469
  );
3209
3470
  if ((patientSensitiveInfo == null ? void 0 : patientSensitiveInfo.email) && patientProfile) {
3210
3471
  Logger.info(
@@ -3229,9 +3490,24 @@ var AppointmentAggregationService = class {
3229
3490
  }
3230
3491
  if (timeChanged && !statusChanged) {
3231
3492
  Logger.info(`[AggService] Appointment ${after.id} time changed.`);
3232
- Logger.warn(
3233
- `[AggService] Independent time change detected for ${after.id}. Review implications for requirements and calendar.`
3234
- );
3493
+ if (after.status === "confirmed" /* CONFIRMED */) {
3494
+ await this.updateRelatedPatientRequirementInstances(
3495
+ before,
3496
+ "supersededReschedule" /* SUPERSEDED_RESCHEDULE */
3497
+ );
3498
+ await this.createPreAppointmentRequirementInstances(after);
3499
+ await this.calendarAdminService.updateAppointmentCalendarEventsTime(
3500
+ after,
3501
+ {
3502
+ start: after.appointmentStartTime,
3503
+ end: after.appointmentEndTime
3504
+ }
3505
+ );
3506
+ } else {
3507
+ Logger.warn(
3508
+ `[AggService] Independent time change detected for ${after.id} with status ${after.status}. Review implications for requirements and calendar.`
3509
+ );
3510
+ }
3235
3511
  }
3236
3512
  Logger.info(
3237
3513
  `[AggService] Successfully processed UPDATE for appointment: ${after.id}`
@@ -3256,9 +3532,19 @@ var AppointmentAggregationService = class {
3256
3532
  deletedAppointment,
3257
3533
  "cancelledAppointment" /* CANCELLED_APPOINTMENT */
3258
3534
  );
3259
- await this.managePatientClinicPractitionerLinks(
3260
- deletedAppointment,
3261
- "cancel"
3535
+ const patientProfile = await this.fetchPatientProfile(
3536
+ deletedAppointment.patientId
3537
+ );
3538
+ if (patientProfile) {
3539
+ await this.managePatientClinicPractitionerLinks(
3540
+ patientProfile,
3541
+ deletedAppointment.practitionerId,
3542
+ deletedAppointment.clinicBranchId,
3543
+ "cancel"
3544
+ );
3545
+ }
3546
+ await this.calendarAdminService.deleteAppointmentCalendarEvents(
3547
+ deletedAppointment
3262
3548
  );
3263
3549
  }
3264
3550
  // --- Helper Methods for Aggregation Logic ---
@@ -3539,18 +3825,47 @@ var AppointmentAggregationService = class {
3539
3825
  try {
3540
3826
  const batch = this.db.batch();
3541
3827
  let instancesCreatedCount = 0;
3828
+ let createdInstances = [];
3829
+ Logger.info(
3830
+ `[AggService] Found ${appointment.postProcedureRequirements.length} post-requirements to process: ${JSON.stringify(
3831
+ appointment.postProcedureRequirements.map((r) => {
3832
+ var _a2, _b;
3833
+ return {
3834
+ id: r.id,
3835
+ name: r.name,
3836
+ type: r.type,
3837
+ isActive: r.isActive,
3838
+ hasTimeframe: !!r.timeframe,
3839
+ notifyAtLength: ((_b = (_a2 = r.timeframe) == null ? void 0 : _a2.notifyAt) == null ? void 0 : _b.length) || 0
3840
+ };
3841
+ })
3842
+ )}`
3843
+ );
3542
3844
  for (const template of appointment.postProcedureRequirements) {
3543
- if (!template) continue;
3845
+ if (!template) {
3846
+ Logger.warn(
3847
+ `[AggService] Found null/undefined template in postProcedureRequirements array`
3848
+ );
3849
+ continue;
3850
+ }
3544
3851
  if (template.type !== "post" /* POST */ || !template.isActive) {
3545
3852
  Logger.debug(
3546
3853
  `[AggService] Skipping template ${template.id} (${template.name}): not an active POST requirement.`
3547
3854
  );
3548
3855
  continue;
3549
3856
  }
3857
+ if (!template.timeframe || !template.timeframe.notifyAt || template.timeframe.notifyAt.length === 0) {
3858
+ Logger.warn(
3859
+ `[AggService] Template ${template.id} (${template.name}) has no timeframe.notifyAt values. Creating with empty instructions.`
3860
+ );
3861
+ }
3550
3862
  Logger.debug(
3551
- `[AggService] Processing POST template ${template.id} (${template.name}) for appt ${appointment.id}`
3863
+ `[AggService] Processing template ${template.id} (${template.name}) for appt ${appointment.id}`
3552
3864
  );
3553
3865
  const newInstanceRef = this.db.collection(PATIENTS_COLLECTION).doc(appointment.patientId).collection(PATIENT_REQUIREMENTS_SUBCOLLECTION_NAME).doc();
3866
+ Logger.debug(
3867
+ `[AggService] Created doc reference: ${newInstanceRef.path}`
3868
+ );
3554
3869
  const instructions = (((_a = template.timeframe) == null ? void 0 : _a.notifyAt) || []).map((notifyAtValue) => {
3555
3870
  let dueTime = appointment.appointmentEndTime;
3556
3871
  if (template.timeframe && typeof notifyAtValue === "number") {
@@ -3565,7 +3880,7 @@ var AppointmentAggregationService = class {
3565
3880
  dueTime = admin10.firestore.Timestamp.fromDate(dueDateTime);
3566
3881
  }
3567
3882
  const actionableWindowHours = template.importance === "high" ? 1 : template.importance === "medium" ? 4 : 15;
3568
- return {
3883
+ const instructionObject = {
3569
3884
  instructionId: `${template.id}_${notifyAtValue}_${newInstanceRef.id}`.replace(
3570
3885
  /[^a-zA-Z0-9_]/g,
3571
3886
  "_"
@@ -3577,36 +3892,142 @@ var AppointmentAggregationService = class {
3577
3892
  originalNotifyAtValue: notifyAtValue,
3578
3893
  originalTimeframeUnit: template.timeframe.unit,
3579
3894
  updatedAt: admin10.firestore.Timestamp.now(),
3580
- // Use current server timestamp
3581
3895
  notificationId: void 0,
3582
3896
  actionTakenAt: void 0
3583
3897
  };
3898
+ return instructionObject;
3584
3899
  });
3585
3900
  const newInstanceData = {
3901
+ id: newInstanceRef.id,
3586
3902
  patientId: appointment.patientId,
3587
3903
  appointmentId: appointment.id,
3588
3904
  originalRequirementId: template.id,
3589
3905
  requirementName: template.name,
3590
3906
  requirementDescription: template.description,
3591
3907
  requirementType: template.type,
3592
- // Should be RequirementType.POST
3593
3908
  requirementImportance: template.importance,
3594
3909
  overallStatus: "active" /* ACTIVE */,
3595
3910
  instructions,
3596
3911
  createdAt: admin10.firestore.FieldValue.serverTimestamp(),
3597
3912
  updatedAt: admin10.firestore.FieldValue.serverTimestamp()
3598
3913
  };
3914
+ Logger.debug(
3915
+ `[AggService] Setting data for requirement: ${JSON.stringify({
3916
+ id: newInstanceRef.id,
3917
+ patientId: newInstanceData.patientId,
3918
+ appointmentId: newInstanceData.appointmentId,
3919
+ requirementName: newInstanceData.requirementName,
3920
+ instructionsCount: newInstanceData.instructions.length
3921
+ })}`
3922
+ );
3599
3923
  batch.set(newInstanceRef, newInstanceData);
3924
+ createdInstances.push({
3925
+ ref: newInstanceRef,
3926
+ data: newInstanceData
3927
+ });
3600
3928
  instancesCreatedCount++;
3601
3929
  Logger.debug(
3602
- `[AggService] Added POST PatientRequirementInstance ${newInstanceRef.id} to batch for template ${template.id}.`
3930
+ `[AggService] Added PatientRequirementInstance ${newInstanceRef.id} to batch for template ${template.id}.`
3603
3931
  );
3604
3932
  }
3605
3933
  if (instancesCreatedCount > 0) {
3606
- await batch.commit();
3607
- Logger.info(
3608
- `[AggService] Successfully created ${instancesCreatedCount} POST_APPOINTMENT requirement instances for appointment ${appointment.id}.`
3609
- );
3934
+ try {
3935
+ await batch.commit();
3936
+ Logger.info(
3937
+ `[AggService] Successfully created ${instancesCreatedCount} POST_APPOINTMENT requirement instances for appointment ${appointment.id}.`
3938
+ );
3939
+ try {
3940
+ const verifySnapshot = await this.db.collection(PATIENTS_COLLECTION).doc(appointment.patientId).collection(PATIENT_REQUIREMENTS_SUBCOLLECTION_NAME).where("appointmentId", "==", appointment.id).get();
3941
+ if (verifySnapshot.empty) {
3942
+ Logger.warn(
3943
+ `[AggService] Batch commit reported success but documents not found! Attempting direct creation as fallback...`
3944
+ );
3945
+ const fallbackPromises = createdInstances.map(
3946
+ async ({ ref, data }) => {
3947
+ try {
3948
+ await ref.set(data);
3949
+ Logger.info(
3950
+ `[AggService] Fallback direct creation success for ${ref.id}`
3951
+ );
3952
+ return true;
3953
+ } catch (fallbackError) {
3954
+ Logger.error(
3955
+ `[AggService] Fallback direct creation failed for ${ref.id}:`,
3956
+ fallbackError
3957
+ );
3958
+ return false;
3959
+ }
3960
+ }
3961
+ );
3962
+ const fallbackResults = await Promise.allSettled(
3963
+ fallbackPromises
3964
+ );
3965
+ const successCount = fallbackResults.filter(
3966
+ (r) => r.status === "fulfilled" && r.value === true
3967
+ ).length;
3968
+ if (successCount > 0) {
3969
+ Logger.info(
3970
+ `[AggService] Fallback mechanism created ${successCount} out of ${createdInstances.length} requirements`
3971
+ );
3972
+ } else {
3973
+ Logger.error(
3974
+ `[AggService] Both batch and fallback mechanisms failed to create requirements`
3975
+ );
3976
+ throw new Error(
3977
+ "Failed to create patient requirements through both batch and direct methods"
3978
+ );
3979
+ }
3980
+ } else {
3981
+ Logger.info(
3982
+ `[AggService] Verification confirmed ${verifySnapshot.size} requirement documents created`
3983
+ );
3984
+ }
3985
+ } catch (verifyError) {
3986
+ Logger.error(
3987
+ `[AggService] Error during verification of created requirements:`,
3988
+ verifyError
3989
+ );
3990
+ }
3991
+ } catch (commitError) {
3992
+ Logger.error(
3993
+ `[AggService] Error committing batch for POST_APPOINTMENT requirement instances for appointment ${appointment.id}:`,
3994
+ commitError
3995
+ );
3996
+ Logger.info(`[AggService] Attempting direct creation as fallback...`);
3997
+ const fallbackPromises = createdInstances.map(
3998
+ async ({ ref, data }) => {
3999
+ try {
4000
+ await ref.set(data);
4001
+ Logger.info(
4002
+ `[AggService] Fallback direct creation success for ${ref.id}`
4003
+ );
4004
+ return true;
4005
+ } catch (fallbackError) {
4006
+ Logger.error(
4007
+ `[AggService] Fallback direct creation failed for ${ref.id}:`,
4008
+ fallbackError
4009
+ );
4010
+ return false;
4011
+ }
4012
+ }
4013
+ );
4014
+ const fallbackResults = await Promise.allSettled(fallbackPromises);
4015
+ const successCount = fallbackResults.filter(
4016
+ (r) => r.status === "fulfilled" && r.value === true
4017
+ ).length;
4018
+ if (successCount > 0) {
4019
+ Logger.info(
4020
+ `[AggService] Fallback mechanism created ${successCount} out of ${createdInstances.length} requirements`
4021
+ );
4022
+ } else {
4023
+ Logger.error(
4024
+ `[AggService] Both batch and fallback mechanisms failed to create requirements`
4025
+ );
4026
+ throw new Error(
4027
+ "Failed to create patient requirements through both batch and direct methods"
4028
+ );
4029
+ }
4030
+ }
3610
4031
  } else {
3611
4032
  Logger.info(
3612
4033
  `[AggService] No new POST_APPOINTMENT requirement instances were prepared for batch commit for appointment ${appointment.id}.`
@@ -3617,6 +4038,7 @@ var AppointmentAggregationService = class {
3617
4038
  `[AggService] Error creating POST_APPOINTMENT requirement instances for appointment ${appointment.id}:`,
3618
4039
  error
3619
4040
  );
4041
+ throw error;
3620
4042
  }
3621
4043
  }
3622
4044
  /**
@@ -3682,75 +4104,165 @@ var AppointmentAggregationService = class {
3682
4104
  }
3683
4105
  }
3684
4106
  /**
3685
- * Manages denormalized links between a patient and the clinic/practitioner associated with an appointment.
3686
- * Adds patientId to arrays on clinic and practitioner documents on appointment creation.
3687
- * Removes patientId on appointment cancellation (basic removal, can be enhanced).
4107
+ * Manages relationships between a patient and clinics/practitioners.
4108
+ * Only updates the patient profile with doctorIds and clinicIds.
3688
4109
  *
3689
- * @param {Appointment} appointment - The appointment object.
3690
- * @param {"create" | "cancel"} action - 'create' to add links, 'cancel' to remove them.
4110
+ * @param {PatientProfile} patientProfile - The patient profile to update
4111
+ * @param {string} practitionerId - The practitioner ID
4112
+ * @param {string} clinicId - The clinic ID
4113
+ * @param {"create" | "cancel"} action - 'create' to add IDs, 'cancel' to potentially remove them
4114
+ * @param {AppointmentStatus} [cancelStatus] - The appointment status if action is 'cancel'
3691
4115
  * @returns {Promise<void>} A promise that resolves when the operation is complete.
3692
4116
  */
3693
- async managePatientClinicPractitionerLinks(appointment, action) {
4117
+ async managePatientClinicPractitionerLinks(patientProfile, practitionerId, clinicId, action, cancelStatus) {
3694
4118
  Logger.info(
3695
- `[AggService] Managing patient-clinic-practitioner links for appt ${appointment.id} (patient: ${appointment.patientId}), action: ${action}`
4119
+ `[AggService] Managing patient-clinic-practitioner links for patient ${patientProfile.id}, action: ${action}`
3696
4120
  );
3697
- if (!appointment.patientId || !appointment.practitionerId || !appointment.clinicBranchId) {
4121
+ try {
4122
+ if (action === "create") {
4123
+ await this.addPatientLinks(patientProfile, practitionerId, clinicId);
4124
+ } else if (action === "cancel") {
4125
+ await this.removePatientLinksIfNoActiveAppointments(
4126
+ patientProfile,
4127
+ practitionerId,
4128
+ clinicId
4129
+ );
4130
+ }
4131
+ } catch (error) {
3698
4132
  Logger.error(
3699
- "[AggService] managePatientClinicPractitionerLinks called with missing IDs.",
3700
- {
3701
- patientId: appointment.patientId,
3702
- practitionerId: appointment.practitionerId,
3703
- clinicBranchId: appointment.clinicBranchId
3704
- }
4133
+ `[AggService] Error managing patient-clinic-practitioner links for patient ${patientProfile.id}:`,
4134
+ error
3705
4135
  );
3706
- return;
3707
4136
  }
3708
- const batch = this.db.batch();
3709
- const patientId = appointment.patientId;
3710
- const practitionerRef = this.db.collection(PRACTITIONERS_COLLECTION).doc(appointment.practitionerId);
3711
- const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(appointment.clinicBranchId);
3712
- if (action === "create") {
3713
- batch.update(practitionerRef, {
3714
- patientIds: admin10.firestore.FieldValue.arrayUnion(patientId),
3715
- // Optionally, update a count or last interaction timestamp
3716
- updatedAt: admin10.firestore.FieldValue.serverTimestamp()
3717
- });
3718
- Logger.debug(
3719
- `[AggService] Adding patient ${patientId} to practitioner ${appointment.practitionerId} patientIds.`
3720
- );
3721
- batch.update(clinicRef, {
3722
- patientIds: admin10.firestore.FieldValue.arrayUnion(patientId),
3723
- updatedAt: admin10.firestore.FieldValue.serverTimestamp()
3724
- });
3725
- Logger.debug(
3726
- `[AggService] Adding patient ${patientId} to clinic ${appointment.clinicBranchId} patientIds.`
3727
- );
3728
- } else if (action === "cancel") {
3729
- batch.update(practitionerRef, {
3730
- patientIds: admin10.firestore.FieldValue.arrayRemove(patientId),
3731
- updatedAt: admin10.firestore.FieldValue.serverTimestamp()
3732
- });
3733
- Logger.debug(
3734
- `[AggService] Removing patient ${patientId} from practitioner ${appointment.practitionerId} patientIds.`
3735
- );
3736
- batch.update(clinicRef, {
3737
- patientIds: admin10.firestore.FieldValue.arrayRemove(patientId),
3738
- updatedAt: admin10.firestore.FieldValue.serverTimestamp()
3739
- });
3740
- Logger.debug(
3741
- `[AggService] Removing patient ${patientId} from clinic ${appointment.clinicBranchId} patientIds.`
4137
+ }
4138
+ /**
4139
+ * Adds practitioner and clinic IDs to the patient profile.
4140
+ *
4141
+ * @param {PatientProfile} patientProfile - The patient profile to update
4142
+ * @param {string} practitionerId - The practitioner ID to add
4143
+ * @param {string} clinicId - The clinic ID to add
4144
+ * @returns {Promise<void>} A promise that resolves when the operation is complete.
4145
+ */
4146
+ async addPatientLinks(patientProfile, practitionerId, clinicId) {
4147
+ var _a, _b;
4148
+ try {
4149
+ const hasDoctor = ((_a = patientProfile.doctorIds) == null ? void 0 : _a.includes(practitionerId)) || false;
4150
+ const hasClinic = ((_b = patientProfile.clinicIds) == null ? void 0 : _b.includes(clinicId)) || false;
4151
+ if (!hasDoctor || !hasClinic) {
4152
+ const patientRef = this.db.collection(PATIENTS_COLLECTION).doc(patientProfile.id);
4153
+ const updateData = {
4154
+ updatedAt: admin10.firestore.FieldValue.serverTimestamp()
4155
+ };
4156
+ if (!hasDoctor) {
4157
+ Logger.debug(
4158
+ `[AggService] Adding practitioner ${practitionerId} to patient ${patientProfile.id}`
4159
+ );
4160
+ updateData.doctorIds = admin10.firestore.FieldValue.arrayUnion(practitionerId);
4161
+ }
4162
+ if (!hasClinic) {
4163
+ Logger.debug(
4164
+ `[AggService] Adding clinic ${clinicId} to patient ${patientProfile.id}`
4165
+ );
4166
+ updateData.clinicIds = admin10.firestore.FieldValue.arrayUnion(clinicId);
4167
+ }
4168
+ await patientRef.update(updateData);
4169
+ Logger.info(
4170
+ `[AggService] Successfully updated patient ${patientProfile.id} with new links.`
4171
+ );
4172
+ } else {
4173
+ Logger.info(
4174
+ `[AggService] Patient ${patientProfile.id} already has links to both practitioner ${practitionerId} and clinic ${clinicId}.`
4175
+ );
4176
+ }
4177
+ } catch (error) {
4178
+ Logger.error(
4179
+ `[AggService] Error updating patient ${patientProfile.id} with new links:`,
4180
+ error
3742
4181
  );
4182
+ throw error;
3743
4183
  }
4184
+ }
4185
+ /**
4186
+ * Removes practitioner and clinic IDs from the patient profile if there are no more active appointments.
4187
+ *
4188
+ * @param {PatientProfile} patientProfile - The patient profile to update
4189
+ * @param {string} practitionerId - The practitioner ID to remove
4190
+ * @param {string} clinicId - The clinic ID to remove
4191
+ * @returns {Promise<void>} A promise that resolves when the operation is complete.
4192
+ */
4193
+ async removePatientLinksIfNoActiveAppointments(patientProfile, practitionerId, clinicId) {
4194
+ var _a, _b;
3744
4195
  try {
3745
- await batch.commit();
4196
+ const activePractitionerAppointments = await this.checkActiveAppointments(
4197
+ patientProfile.id,
4198
+ "practitionerId",
4199
+ practitionerId
4200
+ );
4201
+ const activeClinicAppointments = await this.checkActiveAppointments(
4202
+ patientProfile.id,
4203
+ "clinicBranchId",
4204
+ clinicId
4205
+ );
3746
4206
  Logger.info(
3747
- `[AggService] Successfully updated patient-clinic-practitioner links for appointment ${appointment.id}, action: ${action}.`
4207
+ `[AggService] Active appointment count for patient ${patientProfile.id}: With practitioner ${practitionerId}: ${activePractitionerAppointments}, With clinic ${clinicId}: ${activeClinicAppointments}`
3748
4208
  );
4209
+ const patientRef = this.db.collection(PATIENTS_COLLECTION).doc(patientProfile.id);
4210
+ const updateData = {};
4211
+ let updateNeeded = false;
4212
+ if (activePractitionerAppointments === 0 && ((_a = patientProfile.doctorIds) == null ? void 0 : _a.includes(practitionerId))) {
4213
+ Logger.debug(
4214
+ `[AggService] Removing practitioner ${practitionerId} from patient ${patientProfile.id}`
4215
+ );
4216
+ updateData.doctorIds = admin10.firestore.FieldValue.arrayRemove(practitionerId);
4217
+ updateNeeded = true;
4218
+ }
4219
+ if (activeClinicAppointments === 0 && ((_b = patientProfile.clinicIds) == null ? void 0 : _b.includes(clinicId))) {
4220
+ Logger.debug(
4221
+ `[AggService] Removing clinic ${clinicId} from patient ${patientProfile.id}`
4222
+ );
4223
+ updateData.clinicIds = admin10.firestore.FieldValue.arrayRemove(clinicId);
4224
+ updateNeeded = true;
4225
+ }
4226
+ if (updateNeeded) {
4227
+ updateData.updatedAt = admin10.firestore.FieldValue.serverTimestamp();
4228
+ await patientRef.update(updateData);
4229
+ Logger.info(
4230
+ `[AggService] Successfully removed links from patient ${patientProfile.id}`
4231
+ );
4232
+ } else {
4233
+ Logger.info(
4234
+ `[AggService] No links need to be removed from patient ${patientProfile.id}`
4235
+ );
4236
+ }
3749
4237
  } catch (error) {
3750
4238
  Logger.error(
3751
- `[AggService] Error managing patient-clinic-practitioner links for appointment ${appointment.id}:`,
4239
+ `[AggService] Error removing links from patient profile:`,
3752
4240
  error
3753
4241
  );
4242
+ throw error;
4243
+ }
4244
+ }
4245
+ /**
4246
+ * Checks if there are active appointments between a patient and another entity (practitioner or clinic).
4247
+ *
4248
+ * @param {string} patientId - The patient ID.
4249
+ * @param {"practitionerId" | "clinicBranchId"} entityField - The field to check for the entity ID.
4250
+ * @param {string} entityId - The entity ID (practitioner or clinic).
4251
+ * @returns {Promise<number>} The number of active appointments found.
4252
+ */
4253
+ async checkActiveAppointments(patientId, entityField, entityId) {
4254
+ try {
4255
+ const inactiveStatuses = [
4256
+ "canceled_clinic" /* CANCELED_CLINIC */,
4257
+ "canceled_patient" /* CANCELED_PATIENT */,
4258
+ "canceled_patient_rescheduled" /* CANCELED_PATIENT_RESCHEDULED */,
4259
+ "no_show" /* NO_SHOW */
4260
+ ];
4261
+ const snapshot = await this.db.collection("appointments").where("patientId", "==", patientId).where(entityField, "==", entityId).where("status", "not-in", inactiveStatuses).get();
4262
+ return snapshot.size;
4263
+ } catch (error) {
4264
+ Logger.error(`[AggService] Error checking active appointments:`, error);
4265
+ throw error;
3754
4266
  }
3755
4267
  }
3756
4268
  // --- Data Fetching Helpers (Consider moving to a data access layer or using existing services if available) ---