@blackcode_sa/metaestetics-api 1.13.4 → 1.13.6

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.
@@ -5,7 +5,7 @@ import {
5
5
  Practitioner,
6
6
  } from "../../../types/practitioner"; // Corrected path
7
7
  import { PROCEDURES_COLLECTION } from "../../../types/procedure"; // Added procedure collection
8
- import { CLINIC_GROUPS_COLLECTION, ClinicGroup } from "../../../types/clinic"; // Added clinic group collection
8
+ import { CLINIC_GROUPS_COLLECTION, ClinicGroup, CLINICS_COLLECTION } from "../../../types/clinic"; // Added clinic group collection
9
9
  import { ClinicLocation } from "../../../types/clinic"; // Added ClinicLocation type
10
10
  import { CALENDAR_COLLECTION } from "../../../types/calendar"; // Added calendar collection
11
11
  import { PATIENTS_COLLECTION } from "../../../types/patient"; // Added patient collection
@@ -430,10 +430,12 @@ export class ClinicAggregationService {
430
430
  * Removes clinic from practitioners when a clinic is deleted.
431
431
  * @param practitionerIds IDs of practitioners associated with the clinic.
432
432
  * @param clinicId The ID of the deleted clinic.
433
+ * @param checkAndDeactivateDoctors If true, checks if doctors have remaining active clinics and deactivates them if not.
433
434
  */
434
435
  async removeClinicFromPractitioners(
435
436
  practitionerIds: string[],
436
- clinicId: string
437
+ clinicId: string,
438
+ checkAndDeactivateDoctors: boolean = false
437
439
  ): Promise<void> {
438
440
  if (!practitionerIds || practitionerIds.length === 0) {
439
441
  console.log(
@@ -443,6 +445,7 @@ export class ClinicAggregationService {
443
445
  }
444
446
 
445
447
  const batch = this.db.batch();
448
+ const practitionersToCheck: string[] = [];
446
449
 
447
450
  console.log(
448
451
  `[ClinicAggregationService] Starting batch removal of Clinic (ID: ${clinicId}) from ${practitionerIds.length} practitioners.`
@@ -474,6 +477,11 @@ export class ClinicAggregationService {
474
477
  clinicsInfo: filteredClinicsInfo,
475
478
  updatedAt: admin.firestore.FieldValue.serverTimestamp(),
476
479
  });
480
+
481
+ // If we need to check for remaining active clinics, collect practitioner IDs
482
+ if (checkAndDeactivateDoctors) {
483
+ practitionersToCheck.push(practitionerId);
484
+ }
477
485
  }
478
486
  }
479
487
 
@@ -482,6 +490,13 @@ export class ClinicAggregationService {
482
490
  console.log(
483
491
  `[ClinicAggregationService] Successfully removed Clinic (ID: ${clinicId}) from ${practitionerIds.length} practitioners.`
484
492
  );
493
+
494
+ // Check and deactivate doctors with no remaining active clinics
495
+ if (checkAndDeactivateDoctors && practitionersToCheck.length > 0) {
496
+ await this.checkAndDeactivateDoctorsWithNoActiveClinics(
497
+ practitionersToCheck
498
+ );
499
+ }
485
500
  } catch (error) {
486
501
  console.error(
487
502
  `[ClinicAggregationService] Error committing batch removal for Clinic (ID: ${clinicId}) from practitioners:`,
@@ -491,6 +506,194 @@ export class ClinicAggregationService {
491
506
  }
492
507
  }
493
508
 
509
+ /**
510
+ * Checks if practitioners have any remaining active clinics and deactivates them if not.
511
+ * @param practitionerIds IDs of practitioners to check
512
+ */
513
+ async checkAndDeactivateDoctorsWithNoActiveClinics(
514
+ practitionerIds: string[]
515
+ ): Promise<void> {
516
+ if (!practitionerIds || practitionerIds.length === 0) {
517
+ return;
518
+ }
519
+
520
+ console.log(
521
+ `[ClinicAggregationService] Checking ${practitionerIds.length} practitioners for remaining active clinics.`
522
+ );
523
+
524
+ const batch = this.db.batch();
525
+ let deactivationCount = 0;
526
+
527
+ for (const practitionerId of practitionerIds) {
528
+ const practitionerRef = this.db
529
+ .collection(PRACTITIONERS_COLLECTION)
530
+ .doc(practitionerId);
531
+ const practitionerDoc = await practitionerRef.get();
532
+
533
+ if (!practitionerDoc.exists) {
534
+ continue;
535
+ }
536
+
537
+ const practitionerData = practitionerDoc.data() as Practitioner;
538
+ const clinicIds = practitionerData?.clinics || [];
539
+
540
+ if (clinicIds.length === 0) {
541
+ // No clinics remaining, deactivate the doctor
542
+ console.log(
543
+ `[ClinicAggregationService] Practitioner ${practitionerId} has no remaining clinics. Deactivating.`
544
+ );
545
+ batch.update(practitionerRef, {
546
+ isActive: false,
547
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
548
+ });
549
+ deactivationCount++;
550
+ } else {
551
+ // Check if any of the remaining clinics are active
552
+ const clinicRefs = clinicIds.map((id: string) =>
553
+ this.db.collection(CLINICS_COLLECTION).doc(id)
554
+ );
555
+ const clinicDocs = await Promise.all(
556
+ clinicRefs.map((ref) => ref.get())
557
+ );
558
+
559
+ const hasActiveClinic = clinicDocs.some(
560
+ (doc) => doc.exists && (doc.data() as any)?.isActive === true
561
+ );
562
+
563
+ if (!hasActiveClinic) {
564
+ // No active clinics remaining, deactivate the doctor
565
+ console.log(
566
+ `[ClinicAggregationService] Practitioner ${practitionerId} has no remaining active clinics. Deactivating.`
567
+ );
568
+ batch.update(practitionerRef, {
569
+ isActive: false,
570
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
571
+ });
572
+ deactivationCount++;
573
+ }
574
+ }
575
+ }
576
+
577
+ if (deactivationCount > 0) {
578
+ try {
579
+ await batch.commit();
580
+ console.log(
581
+ `[ClinicAggregationService] Successfully deactivated ${deactivationCount} practitioners with no remaining active clinics.`
582
+ );
583
+ } catch (error) {
584
+ console.error(
585
+ `[ClinicAggregationService] Error deactivating practitioners:`,
586
+ error
587
+ );
588
+ throw error;
589
+ }
590
+ } else {
591
+ console.log(
592
+ `[ClinicAggregationService] All practitioners have remaining active clinics. No deactivations needed.`
593
+ );
594
+ }
595
+ }
596
+
597
+ /**
598
+ * Checks if practitioners were deactivated because they had no active clinics,
599
+ * and reactivates them if this clinic was their only clinic.
600
+ * @param practitionerIds IDs of practitioners associated with the reactivated clinic
601
+ * @param clinicId ID of the reactivated clinic
602
+ */
603
+ async checkAndReactivateDoctorsForClinic(
604
+ practitionerIds: string[],
605
+ clinicId: string
606
+ ): Promise<void> {
607
+ if (!practitionerIds || practitionerIds.length === 0) {
608
+ return;
609
+ }
610
+
611
+ console.log(
612
+ `[ClinicAggregationService] Checking ${practitionerIds.length} practitioners for reactivation after clinic ${clinicId} was reactivated.`
613
+ );
614
+
615
+ const batch = this.db.batch();
616
+ let reactivationCount = 0;
617
+
618
+ for (const practitionerId of practitionerIds) {
619
+ const practitionerRef = this.db
620
+ .collection(PRACTITIONERS_COLLECTION)
621
+ .doc(practitionerId);
622
+ const practitionerDoc = await practitionerRef.get();
623
+
624
+ if (!practitionerDoc.exists) {
625
+ continue;
626
+ }
627
+
628
+ const practitionerData = practitionerDoc.data() as Practitioner;
629
+
630
+ // Check if practitioner is inactive
631
+ if (practitionerData?.isActive === false) {
632
+ // Check if this clinic is in their clinics list (meaning it was their clinic)
633
+ const clinicIds = practitionerData?.clinics || [];
634
+ if (clinicIds.includes(clinicId)) {
635
+ // Check if they have any other active clinics
636
+ const otherClinicIds = clinicIds.filter((id: string) => id !== clinicId);
637
+
638
+ if (otherClinicIds.length === 0) {
639
+ // This was their only clinic, reactivate them
640
+ console.log(
641
+ `[ClinicAggregationService] Practitioner ${practitionerId} was deactivated because clinic ${clinicId} was their only clinic. Reactivating.`
642
+ );
643
+ batch.update(practitionerRef, {
644
+ isActive: true,
645
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
646
+ });
647
+ reactivationCount++;
648
+ } else {
649
+ // Check if any other clinics are active
650
+ const otherClinicRefs = otherClinicIds.map((id: string) =>
651
+ this.db.collection(CLINICS_COLLECTION).doc(id)
652
+ );
653
+ const otherClinicDocs = await Promise.all(
654
+ otherClinicRefs.map((ref) => ref.get())
655
+ );
656
+
657
+ const hasOtherActiveClinic = otherClinicDocs.some(
658
+ (doc) => doc.exists && (doc.data() as any)?.isActive === true
659
+ );
660
+
661
+ if (!hasOtherActiveClinic) {
662
+ // All their other clinics are also inactive, so this clinic reactivation should reactivate them
663
+ console.log(
664
+ `[ClinicAggregationService] Practitioner ${practitionerId} has no other active clinics. Reactivating because clinic ${clinicId} was reactivated.`
665
+ );
666
+ batch.update(practitionerRef, {
667
+ isActive: true,
668
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
669
+ });
670
+ reactivationCount++;
671
+ }
672
+ }
673
+ }
674
+ }
675
+ }
676
+
677
+ if (reactivationCount > 0) {
678
+ try {
679
+ await batch.commit();
680
+ console.log(
681
+ `[ClinicAggregationService] Successfully reactivated ${reactivationCount} practitioners.`
682
+ );
683
+ } catch (error) {
684
+ console.error(
685
+ `[ClinicAggregationService] Error reactivating practitioners:`,
686
+ error
687
+ );
688
+ throw error;
689
+ }
690
+ } else {
691
+ console.log(
692
+ `[ClinicAggregationService] No practitioners needed reactivation.`
693
+ );
694
+ }
695
+ }
696
+
494
697
  /**
495
698
  * Inactivates all procedures associated with a deleted clinic
496
699
  * @param procedureIds IDs of procedures associated with the clinic
@@ -534,6 +737,66 @@ export class ClinicAggregationService {
534
737
  }
535
738
  }
536
739
 
740
+ /**
741
+ * Reactivates all procedures associated with a reactivated clinic
742
+ * Only reactivates procedures that still exist (haven't been deleted)
743
+ * @param procedureIds IDs of procedures associated with the clinic
744
+ */
745
+ async reactivateProceduresForClinic(procedureIds: string[]): Promise<void> {
746
+ if (!procedureIds || procedureIds.length === 0) {
747
+ console.log(
748
+ "[ClinicAggregationService] No procedure IDs provided for reactivation. Skipping."
749
+ );
750
+ return;
751
+ }
752
+
753
+ const batch = this.db.batch();
754
+ let reactivationCount = 0;
755
+
756
+ console.log(
757
+ `[ClinicAggregationService] Starting reactivation of ${procedureIds.length} procedures.`
758
+ );
759
+
760
+ for (const procedureId of procedureIds) {
761
+ const procedureRef = this.db
762
+ .collection(PROCEDURES_COLLECTION)
763
+ .doc(procedureId);
764
+
765
+ // Check if procedure still exists (hasn't been deleted)
766
+ const procedureDoc = await procedureRef.get();
767
+ if (procedureDoc.exists) {
768
+ batch.update(procedureRef, {
769
+ isActive: true,
770
+ updatedAt: admin.firestore.FieldValue.serverTimestamp(),
771
+ });
772
+ reactivationCount++;
773
+ } else {
774
+ console.log(
775
+ `[ClinicAggregationService] Procedure ${procedureId} no longer exists. Skipping reactivation.`
776
+ );
777
+ }
778
+ }
779
+
780
+ if (reactivationCount > 0) {
781
+ try {
782
+ await batch.commit();
783
+ console.log(
784
+ `[ClinicAggregationService] Successfully reactivated ${reactivationCount} procedures.`
785
+ );
786
+ } catch (error) {
787
+ console.error(
788
+ `[ClinicAggregationService] Error committing batch reactivation of procedures:`,
789
+ error
790
+ );
791
+ throw error;
792
+ }
793
+ } else {
794
+ console.log(
795
+ `[ClinicAggregationService] No procedures to reactivate (all may have been deleted).`
796
+ );
797
+ }
798
+ }
799
+
537
800
  /**
538
801
  * Removes clinic from clinic group when a clinic is deleted
539
802
  * @param clinicGroupId ID of the parent clinic group
@@ -37,6 +37,15 @@ export interface ClinicLocation {
37
37
  tz?: string | null;
38
38
  }
39
39
 
40
+ /**
41
+ * Interface for a break period during working hours
42
+ */
43
+ export interface Break {
44
+ title?: string;
45
+ start: string;
46
+ end: string;
47
+ }
48
+
40
49
  /**
41
50
  * Interface for working hours
42
51
  */
@@ -44,37 +53,37 @@ export interface WorkingHours {
44
53
  monday: {
45
54
  open: string;
46
55
  close: string;
47
- breaks?: { start: string; end: string }[];
56
+ breaks?: Break[];
48
57
  } | null;
49
58
  tuesday: {
50
59
  open: string;
51
60
  close: string;
52
- breaks?: { start: string; end: string }[];
61
+ breaks?: Break[];
53
62
  } | null;
54
63
  wednesday: {
55
64
  open: string;
56
65
  close: string;
57
- breaks?: { start: string; end: string }[];
66
+ breaks?: Break[];
58
67
  } | null;
59
68
  thursday: {
60
69
  open: string;
61
70
  close: string;
62
- breaks?: { start: string; end: string }[];
71
+ breaks?: Break[];
63
72
  } | null;
64
73
  friday: {
65
74
  open: string;
66
75
  close: string;
67
- breaks?: { start: string; end: string }[];
76
+ breaks?: Break[];
68
77
  } | null;
69
78
  saturday: {
70
79
  open: string;
71
80
  close: string;
72
- breaks?: { start: string; end: string }[];
81
+ breaks?: Break[];
73
82
  } | null;
74
83
  sunday: {
75
84
  open: string;
76
85
  close: string;
77
- breaks?: { start: string; end: string }[];
86
+ breaks?: Break[];
78
87
  } | null;
79
88
  }
80
89
 
@@ -47,6 +47,7 @@ const workingHoursTimeSchema = z.object({
47
47
  breaks: z
48
48
  .array(
49
49
  z.object({
50
+ title: z.string().optional(),
50
51
  start: z.string(),
51
52
  end: z.string(),
52
53
  }),