@blackcode_sa/metaestetics-api 1.6.2 → 1.6.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 +228 -25
- package/dist/admin/index.d.ts +228 -25
- package/dist/admin/index.js +35867 -2493
- package/dist/admin/index.mjs +35856 -2464
- package/dist/backoffice/index.d.mts +252 -1
- package/dist/backoffice/index.d.ts +252 -1
- package/dist/backoffice/index.js +86 -12
- package/dist/backoffice/index.mjs +86 -13
- package/dist/index.d.mts +1417 -554
- package/dist/index.d.ts +1417 -554
- package/dist/index.js +1393 -687
- package/dist/index.mjs +1423 -711
- package/package.json +1 -1
- package/src/admin/index.ts +15 -1
- package/src/admin/notifications/notifications.admin.ts +1 -1
- package/src/admin/requirements/README.md +128 -0
- package/src/admin/requirements/patient-requirements.admin.service.ts +482 -0
- package/src/index.ts +16 -1
- package/src/services/appointment/appointment.service.ts +315 -86
- package/src/services/clinic/clinic-admin.service.ts +3 -0
- package/src/services/clinic/clinic-group.service.ts +8 -0
- package/src/services/documentation-templates/documentation-template.service.ts +24 -16
- package/src/services/documentation-templates/filled-document.service.ts +253 -136
- package/src/services/patient/patient.service.ts +31 -1
- package/src/services/patient/patientRequirements.service.ts +285 -0
- package/src/services/patient/utils/practitioner.utils.ts +79 -1
- package/src/types/appointment/index.ts +134 -10
- package/src/types/documentation-templates/index.ts +34 -2
- package/src/types/notifications/README.md +77 -0
- package/src/types/notifications/index.ts +154 -27
- package/src/types/patient/index.ts +6 -0
- package/src/types/patient/patient-requirements.ts +81 -0
- package/src/validations/appointment.schema.ts +300 -62
- package/src/validations/documentation-templates/template.schema.ts +55 -0
- package/src/validations/documentation-templates.schema.ts +9 -14
- package/src/validations/notification.schema.ts +3 -3
- package/src/validations/patient/patient-requirements.schema.ts +75 -0
package/src/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ export { CalendarServiceV2 } from "./services/calendar/calendar-refactored.servi
|
|
|
33
33
|
export { SyncedCalendarsService } from "./services/calendar/synced-calendars.service";
|
|
34
34
|
export { ReviewService } from "./services/reviews/reviews.service";
|
|
35
35
|
export { AppointmentService } from "./services/appointment/appointment.service";
|
|
36
|
+
export { PatientRequirementsService } from "./services/patient/patientRequirements.service";
|
|
36
37
|
|
|
37
38
|
// Backoffice services
|
|
38
39
|
export { BrandService } from "./backoffice/services/brand.service";
|
|
@@ -74,7 +75,6 @@ export type {
|
|
|
74
75
|
PreRequirementNotification,
|
|
75
76
|
PostRequirementNotification,
|
|
76
77
|
AppointmentReminderNotification,
|
|
77
|
-
AppointmentNotification,
|
|
78
78
|
} from "./types/notifications";
|
|
79
79
|
export { NotificationType, NotificationStatus } from "./types/notifications";
|
|
80
80
|
|
|
@@ -346,3 +346,18 @@ export {
|
|
|
346
346
|
reviewSchema,
|
|
347
347
|
createReviewSchema,
|
|
348
348
|
} from "./validations/reviews.schema";
|
|
349
|
+
|
|
350
|
+
// Patient Requirement Types
|
|
351
|
+
export type {
|
|
352
|
+
PatientRequirementInstance,
|
|
353
|
+
PatientRequirementInstruction,
|
|
354
|
+
} from "./types/patient/patient-requirements";
|
|
355
|
+
export {
|
|
356
|
+
PatientInstructionStatus,
|
|
357
|
+
PatientRequirementOverallStatus,
|
|
358
|
+
PATIENT_REQUIREMENTS_SUBCOLLECTION_NAME,
|
|
359
|
+
} from "./types/patient/patient-requirements";
|
|
360
|
+
|
|
361
|
+
// Patient Requirement Validation Schemas (if they exist and are for client use)
|
|
362
|
+
// Assuming the path, add if it exists and is needed:
|
|
363
|
+
// export * from "./validations/patient/patient-requirements.schema";
|
|
@@ -3,6 +3,8 @@ import {
|
|
|
3
3
|
Timestamp,
|
|
4
4
|
DocumentSnapshot,
|
|
5
5
|
serverTimestamp,
|
|
6
|
+
arrayUnion,
|
|
7
|
+
arrayRemove,
|
|
6
8
|
} from "firebase/firestore";
|
|
7
9
|
import { Auth } from "firebase/auth";
|
|
8
10
|
import { FirebaseApp } from "firebase/app";
|
|
@@ -15,6 +17,9 @@ import {
|
|
|
15
17
|
UpdateAppointmentData,
|
|
16
18
|
SearchAppointmentsParams,
|
|
17
19
|
PaymentStatus,
|
|
20
|
+
AppointmentMediaItem,
|
|
21
|
+
PatientReviewInfo,
|
|
22
|
+
LinkedFormInfo,
|
|
18
23
|
} from "../../types/appointment";
|
|
19
24
|
import {
|
|
20
25
|
createAppointmentSchema,
|
|
@@ -27,6 +32,7 @@ import { CalendarServiceV2 } from "../calendar/calendar-refactored.service";
|
|
|
27
32
|
import { PatientService } from "../patient/patient.service";
|
|
28
33
|
import { PractitionerService } from "../practitioner/practitioner.service";
|
|
29
34
|
import { ClinicService } from "../clinic/clinic.service";
|
|
35
|
+
import { FilledDocumentService } from "../documentation-templates/filled-document.service";
|
|
30
36
|
|
|
31
37
|
// Import utility functions
|
|
32
38
|
import {
|
|
@@ -54,6 +60,7 @@ export class AppointmentService extends BaseService {
|
|
|
54
60
|
private patientService: PatientService;
|
|
55
61
|
private practitionerService: PractitionerService;
|
|
56
62
|
private clinicService: ClinicService;
|
|
63
|
+
private filledDocumentService: FilledDocumentService;
|
|
57
64
|
private functions: Functions;
|
|
58
65
|
|
|
59
66
|
/**
|
|
@@ -74,13 +81,15 @@ export class AppointmentService extends BaseService {
|
|
|
74
81
|
calendarService: CalendarServiceV2,
|
|
75
82
|
patientService: PatientService,
|
|
76
83
|
practitionerService: PractitionerService,
|
|
77
|
-
clinicService: ClinicService
|
|
84
|
+
clinicService: ClinicService,
|
|
85
|
+
filledDocumentService: FilledDocumentService
|
|
78
86
|
) {
|
|
79
87
|
super(db, auth, app);
|
|
80
88
|
this.calendarService = calendarService;
|
|
81
89
|
this.patientService = patientService;
|
|
82
90
|
this.practitionerService = practitionerService;
|
|
83
91
|
this.clinicService = clinicService;
|
|
92
|
+
this.filledDocumentService = filledDocumentService;
|
|
84
93
|
this.functions = getFunctions(app, "europe-west6"); // Initialize Firebase Functions with the correct region
|
|
85
94
|
}
|
|
86
95
|
|
|
@@ -580,61 +589,67 @@ export class AppointmentService extends BaseService {
|
|
|
580
589
|
*
|
|
581
590
|
* @param appointmentId ID of the appointment
|
|
582
591
|
* @param newStatus New status to set
|
|
583
|
-
* @param
|
|
584
|
-
* @param canceledBy Required if canceling the appointment
|
|
592
|
+
* @param details Optional details for the status change
|
|
585
593
|
* @returns The updated appointment
|
|
586
594
|
*/
|
|
587
595
|
async updateAppointmentStatus(
|
|
588
596
|
appointmentId: string,
|
|
589
597
|
newStatus: AppointmentStatus,
|
|
590
|
-
|
|
591
|
-
|
|
598
|
+
details?: {
|
|
599
|
+
cancellationReason?: string;
|
|
600
|
+
canceledBy?: "patient" | "clinic" | "practitioner" | "system";
|
|
601
|
+
}
|
|
592
602
|
): Promise<Appointment> {
|
|
593
603
|
console.log(
|
|
594
604
|
`[APPOINTMENT_SERVICE] Updating status of appointment ${appointmentId} to ${newStatus}`
|
|
595
605
|
);
|
|
606
|
+
const updateData: UpdateAppointmentData = {
|
|
607
|
+
status: newStatus,
|
|
608
|
+
updatedAt: serverTimestamp(),
|
|
609
|
+
};
|
|
596
610
|
|
|
597
|
-
// Create update data based on the new status
|
|
598
|
-
const updateData: UpdateAppointmentData = { status: newStatus };
|
|
599
|
-
|
|
600
|
-
// Add cancellation details if applicable
|
|
601
611
|
if (
|
|
602
612
|
newStatus === AppointmentStatus.CANCELED_CLINIC ||
|
|
603
|
-
newStatus === AppointmentStatus.CANCELED_PATIENT
|
|
613
|
+
newStatus === AppointmentStatus.CANCELED_PATIENT ||
|
|
614
|
+
newStatus === AppointmentStatus.CANCELED_PATIENT_RESCHEDULED
|
|
604
615
|
) {
|
|
605
|
-
if (!cancellationReason) {
|
|
606
|
-
throw new Error(
|
|
607
|
-
"Cancellation reason is required when canceling an appointment"
|
|
608
|
-
);
|
|
616
|
+
if (!details?.cancellationReason) {
|
|
617
|
+
throw new Error("Cancellation reason is required when canceling.");
|
|
609
618
|
}
|
|
610
|
-
if (!canceledBy) {
|
|
611
|
-
throw new Error(
|
|
612
|
-
"Canceled by is required when canceling an appointment"
|
|
613
|
-
);
|
|
619
|
+
if (!details?.canceledBy) {
|
|
620
|
+
throw new Error("Canceled by is required when canceling.");
|
|
614
621
|
}
|
|
615
|
-
|
|
616
|
-
updateData.
|
|
617
|
-
updateData.
|
|
622
|
+
updateData.cancellationReason = details.cancellationReason;
|
|
623
|
+
updateData.canceledBy = details.canceledBy;
|
|
624
|
+
updateData.cancellationTime = Timestamp.now();
|
|
618
625
|
}
|
|
619
626
|
|
|
620
|
-
// Add confirmation time if confirming
|
|
621
627
|
if (newStatus === AppointmentStatus.CONFIRMED) {
|
|
622
628
|
updateData.confirmationTime = Timestamp.now();
|
|
623
629
|
}
|
|
624
630
|
|
|
631
|
+
if (newStatus === AppointmentStatus.RESCHEDULED_BY_CLINIC) {
|
|
632
|
+
updateData.rescheduleTime = Timestamp.now();
|
|
633
|
+
}
|
|
634
|
+
|
|
625
635
|
return this.updateAppointment(appointmentId, updateData);
|
|
626
636
|
}
|
|
627
637
|
|
|
628
638
|
/**
|
|
629
|
-
* Confirms an
|
|
630
|
-
*
|
|
631
|
-
* @param appointmentId ID of the appointment to confirm
|
|
632
|
-
* @returns The confirmed appointment
|
|
639
|
+
* Confirms a PENDING appointment by an Admin/Clinic.
|
|
633
640
|
*/
|
|
634
|
-
async
|
|
641
|
+
async confirmAppointmentAdmin(appointmentId: string): Promise<Appointment> {
|
|
635
642
|
console.log(
|
|
636
|
-
`[APPOINTMENT_SERVICE]
|
|
643
|
+
`[APPOINTMENT_SERVICE] Admin confirming appointment: ${appointmentId}`
|
|
637
644
|
);
|
|
645
|
+
const appointment = await this.getAppointmentById(appointmentId);
|
|
646
|
+
if (!appointment)
|
|
647
|
+
throw new Error(`Appointment ${appointmentId} not found.`);
|
|
648
|
+
if (appointment.status !== AppointmentStatus.PENDING) {
|
|
649
|
+
throw new Error(
|
|
650
|
+
`Appointment ${appointmentId} is not in PENDING state to be confirmed.`
|
|
651
|
+
);
|
|
652
|
+
}
|
|
638
653
|
return this.updateAppointmentStatus(
|
|
639
654
|
appointmentId,
|
|
640
655
|
AppointmentStatus.CONFIRMED
|
|
@@ -642,125 +657,340 @@ export class AppointmentService extends BaseService {
|
|
|
642
657
|
}
|
|
643
658
|
|
|
644
659
|
/**
|
|
645
|
-
* Cancels an appointment
|
|
646
|
-
*
|
|
647
|
-
* @param appointmentId ID of the appointment to cancel
|
|
648
|
-
* @param reason Reason for cancellation
|
|
649
|
-
* @returns The canceled appointment
|
|
660
|
+
* Cancels an appointment by the User (Patient).
|
|
650
661
|
*/
|
|
651
|
-
async
|
|
662
|
+
async cancelAppointmentUser(
|
|
652
663
|
appointmentId: string,
|
|
653
664
|
reason: string
|
|
654
665
|
): Promise<Appointment> {
|
|
655
666
|
console.log(
|
|
656
|
-
`[APPOINTMENT_SERVICE]
|
|
667
|
+
`[APPOINTMENT_SERVICE] User canceling appointment: ${appointmentId}`
|
|
657
668
|
);
|
|
658
669
|
return this.updateAppointmentStatus(
|
|
659
670
|
appointmentId,
|
|
660
|
-
AppointmentStatus.
|
|
661
|
-
|
|
662
|
-
|
|
671
|
+
AppointmentStatus.CANCELED_PATIENT,
|
|
672
|
+
{
|
|
673
|
+
cancellationReason: reason,
|
|
674
|
+
canceledBy: "patient",
|
|
675
|
+
}
|
|
663
676
|
);
|
|
664
677
|
}
|
|
665
678
|
|
|
666
679
|
/**
|
|
667
|
-
* Cancels an appointment
|
|
668
|
-
*
|
|
669
|
-
* @param appointmentId ID of the appointment to cancel
|
|
670
|
-
* @param reason Reason for cancellation
|
|
671
|
-
* @returns The canceled appointment
|
|
680
|
+
* Cancels an appointment by an Admin/Clinic.
|
|
672
681
|
*/
|
|
673
|
-
async
|
|
682
|
+
async cancelAppointmentAdmin(
|
|
674
683
|
appointmentId: string,
|
|
675
684
|
reason: string
|
|
676
685
|
): Promise<Appointment> {
|
|
677
686
|
console.log(
|
|
678
|
-
`[APPOINTMENT_SERVICE]
|
|
687
|
+
`[APPOINTMENT_SERVICE] Admin canceling appointment: ${appointmentId}`
|
|
679
688
|
);
|
|
680
689
|
return this.updateAppointmentStatus(
|
|
681
690
|
appointmentId,
|
|
682
|
-
AppointmentStatus.
|
|
683
|
-
|
|
684
|
-
|
|
691
|
+
AppointmentStatus.CANCELED_CLINIC,
|
|
692
|
+
{
|
|
693
|
+
cancellationReason: reason,
|
|
694
|
+
canceledBy: "clinic",
|
|
695
|
+
}
|
|
685
696
|
);
|
|
686
697
|
}
|
|
687
698
|
|
|
688
699
|
/**
|
|
689
|
-
*
|
|
690
|
-
*
|
|
691
|
-
|
|
692
|
-
|
|
700
|
+
* Admin proposes to reschedule an appointment.
|
|
701
|
+
* Sets status to RESCHEDULED_BY_CLINIC and updates times.
|
|
702
|
+
*/
|
|
703
|
+
async rescheduleAppointmentAdmin(
|
|
704
|
+
appointmentId: string,
|
|
705
|
+
newStartTime: Timestamp,
|
|
706
|
+
newEndTime: Timestamp
|
|
707
|
+
): Promise<Appointment> {
|
|
708
|
+
console.log(
|
|
709
|
+
`[APPOINTMENT_SERVICE] Admin rescheduling appointment: ${appointmentId}`
|
|
710
|
+
);
|
|
711
|
+
if (newEndTime.toMillis() <= newStartTime.toMillis()) {
|
|
712
|
+
throw new Error("New end time must be after new start time.");
|
|
713
|
+
}
|
|
714
|
+
const updateData: UpdateAppointmentData = {
|
|
715
|
+
status: AppointmentStatus.RESCHEDULED_BY_CLINIC,
|
|
716
|
+
appointmentStartTime: newStartTime,
|
|
717
|
+
appointmentEndTime: newEndTime,
|
|
718
|
+
rescheduleTime: Timestamp.now(),
|
|
719
|
+
confirmationTime: null,
|
|
720
|
+
updatedAt: serverTimestamp(),
|
|
721
|
+
};
|
|
722
|
+
return this.updateAppointment(appointmentId, updateData);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* User confirms a reschedule proposed by the clinic.
|
|
727
|
+
* Status changes from RESCHEDULED_BY_CLINIC to CONFIRMED.
|
|
693
728
|
*/
|
|
694
|
-
async
|
|
729
|
+
async rescheduleAppointmentConfirmUser(
|
|
730
|
+
appointmentId: string
|
|
731
|
+
): Promise<Appointment> {
|
|
695
732
|
console.log(
|
|
696
|
-
`[APPOINTMENT_SERVICE]
|
|
733
|
+
`[APPOINTMENT_SERVICE] User confirming reschedule for: ${appointmentId}`
|
|
697
734
|
);
|
|
735
|
+
const appointment = await this.getAppointmentById(appointmentId);
|
|
736
|
+
if (!appointment)
|
|
737
|
+
throw new Error(`Appointment ${appointmentId} not found.`);
|
|
738
|
+
if (appointment.status !== AppointmentStatus.RESCHEDULED_BY_CLINIC) {
|
|
739
|
+
throw new Error(
|
|
740
|
+
`Appointment ${appointmentId} is not in RESCHEDULED_BY_CLINIC state.`
|
|
741
|
+
);
|
|
742
|
+
}
|
|
698
743
|
return this.updateAppointmentStatus(
|
|
699
744
|
appointmentId,
|
|
700
|
-
AppointmentStatus.
|
|
745
|
+
AppointmentStatus.CONFIRMED
|
|
701
746
|
);
|
|
702
747
|
}
|
|
703
748
|
|
|
704
749
|
/**
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
* @param appointmentId ID of the appointment
|
|
708
|
-
* @returns The updated appointment
|
|
750
|
+
* User rejects a reschedule proposed by the clinic.
|
|
751
|
+
* Status changes from RESCHEDULED_BY_CLINIC to CANCELED_PATIENT_RESCHEDULED.
|
|
709
752
|
*/
|
|
710
|
-
async
|
|
711
|
-
|
|
753
|
+
async rescheduleAppointmentRejectUser(
|
|
754
|
+
appointmentId: string,
|
|
755
|
+
reason: string
|
|
756
|
+
): Promise<Appointment> {
|
|
757
|
+
console.log(
|
|
758
|
+
`[APPOINTMENT_SERVICE] User rejecting reschedule for: ${appointmentId}`
|
|
759
|
+
);
|
|
760
|
+
const appointment = await this.getAppointmentById(appointmentId);
|
|
761
|
+
if (!appointment)
|
|
762
|
+
throw new Error(`Appointment ${appointmentId} not found.`);
|
|
763
|
+
if (appointment.status !== AppointmentStatus.RESCHEDULED_BY_CLINIC) {
|
|
764
|
+
throw new Error(
|
|
765
|
+
`Appointment ${appointmentId} is not in RESCHEDULED_BY_CLINIC state.`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
712
768
|
return this.updateAppointmentStatus(
|
|
713
769
|
appointmentId,
|
|
714
|
-
AppointmentStatus.
|
|
770
|
+
AppointmentStatus.CANCELED_PATIENT_RESCHEDULED,
|
|
771
|
+
{
|
|
772
|
+
cancellationReason: reason,
|
|
773
|
+
canceledBy: "patient",
|
|
774
|
+
}
|
|
715
775
|
);
|
|
716
776
|
}
|
|
717
777
|
|
|
718
778
|
/**
|
|
719
|
-
*
|
|
720
|
-
*
|
|
721
|
-
* @param appointmentId ID of the appointment
|
|
722
|
-
* @param actualDurationMinutes Actual duration of the appointment in minutes
|
|
723
|
-
* @returns The updated appointment
|
|
779
|
+
* Admin checks in a patient for their appointment.
|
|
780
|
+
* Requires all pending user forms to be completed.
|
|
724
781
|
*/
|
|
725
|
-
async
|
|
782
|
+
async checkInPatientAdmin(appointmentId: string): Promise<Appointment> {
|
|
783
|
+
console.log(
|
|
784
|
+
`[APPOINTMENT_SERVICE] Admin checking in patient for: ${appointmentId}`
|
|
785
|
+
);
|
|
786
|
+
const appointment = await this.getAppointmentById(appointmentId);
|
|
787
|
+
if (!appointment)
|
|
788
|
+
throw new Error(`Appointment ${appointmentId} not found.`);
|
|
789
|
+
|
|
790
|
+
if (
|
|
791
|
+
appointment.pendingUserFormsIds &&
|
|
792
|
+
appointment.pendingUserFormsIds.length > 0
|
|
793
|
+
) {
|
|
794
|
+
throw new Error(
|
|
795
|
+
`Cannot check in: Patient has ${
|
|
796
|
+
appointment.pendingUserFormsIds.length
|
|
797
|
+
} pending required form(s). IDs: ${appointment.pendingUserFormsIds.join(
|
|
798
|
+
", "
|
|
799
|
+
)}`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
if (
|
|
803
|
+
appointment.status !== AppointmentStatus.CONFIRMED &&
|
|
804
|
+
appointment.status !== AppointmentStatus.RESCHEDULED_BY_CLINIC
|
|
805
|
+
) {
|
|
806
|
+
console.warn(
|
|
807
|
+
`Checking in appointment ${appointmentId} with status ${appointment.status}. Ensure this is intended.`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return this.updateAppointmentStatus(
|
|
812
|
+
appointmentId,
|
|
813
|
+
AppointmentStatus.CHECKED_IN
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Doctor starts the appointment procedure.
|
|
819
|
+
*/
|
|
820
|
+
async startAppointmentDoctor(appointmentId: string): Promise<Appointment> {
|
|
821
|
+
console.log(
|
|
822
|
+
`[APPOINTMENT_SERVICE] Doctor starting appointment: ${appointmentId}`
|
|
823
|
+
);
|
|
824
|
+
const appointment = await this.getAppointmentById(appointmentId);
|
|
825
|
+
if (!appointment)
|
|
826
|
+
throw new Error(`Appointment ${appointmentId} not found.`);
|
|
827
|
+
if (appointment.status !== AppointmentStatus.CHECKED_IN) {
|
|
828
|
+
throw new Error(
|
|
829
|
+
`Appointment ${appointmentId} must be CHECKED_IN to start.`
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
// Update status and set procedureActualStartTime
|
|
833
|
+
const updateData: UpdateAppointmentData = {
|
|
834
|
+
status: AppointmentStatus.IN_PROGRESS,
|
|
835
|
+
procedureActualStartTime: Timestamp.now(), // Set actual start time
|
|
836
|
+
updatedAt: serverTimestamp(),
|
|
837
|
+
};
|
|
838
|
+
return this.updateAppointment(appointmentId, updateData);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Doctor completes and finalizes the appointment.
|
|
843
|
+
*/
|
|
844
|
+
async completeAppointmentDoctor(
|
|
726
845
|
appointmentId: string,
|
|
727
|
-
|
|
846
|
+
finalizationNotes: string,
|
|
847
|
+
actualDurationMinutesInput?: number // Renamed to avoid conflict if we calculate
|
|
728
848
|
): Promise<Appointment> {
|
|
729
849
|
console.log(
|
|
730
|
-
`[APPOINTMENT_SERVICE]
|
|
850
|
+
`[APPOINTMENT_SERVICE] Doctor completing appointment: ${appointmentId}`
|
|
731
851
|
);
|
|
852
|
+
const currentUser = this.auth.currentUser;
|
|
853
|
+
if (!currentUser)
|
|
854
|
+
throw new Error("Authentication required to complete appointment.");
|
|
855
|
+
|
|
856
|
+
const appointment = await this.getAppointmentById(appointmentId);
|
|
857
|
+
if (!appointment)
|
|
858
|
+
throw new Error(`Appointment ${appointmentId} not found.`);
|
|
859
|
+
|
|
860
|
+
let calculatedDurationMinutes = actualDurationMinutesInput;
|
|
861
|
+
const procedureCompletionTime = Timestamp.now();
|
|
862
|
+
|
|
863
|
+
// Calculate duration if not provided and actual start time is available
|
|
864
|
+
if (
|
|
865
|
+
calculatedDurationMinutes === undefined &&
|
|
866
|
+
appointment.procedureActualStartTime
|
|
867
|
+
) {
|
|
868
|
+
const startTimeMillis = appointment.procedureActualStartTime.toMillis();
|
|
869
|
+
const endTimeMillis = procedureCompletionTime.toMillis();
|
|
870
|
+
if (endTimeMillis > startTimeMillis) {
|
|
871
|
+
calculatedDurationMinutes = Math.round(
|
|
872
|
+
(endTimeMillis - startTimeMillis) / 60000
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
732
876
|
|
|
733
877
|
const updateData: UpdateAppointmentData = {
|
|
734
878
|
status: AppointmentStatus.COMPLETED,
|
|
735
|
-
actualDurationMinutes,
|
|
879
|
+
actualDurationMinutes: calculatedDurationMinutes, // Use calculated or provided duration
|
|
880
|
+
finalizedDetails: {
|
|
881
|
+
by: currentUser.uid, // This is used ID, not practitioner's profile ID (just so we know who completed the appointment)
|
|
882
|
+
at: procedureCompletionTime, // Use consistent completion timestamp
|
|
883
|
+
notes: finalizationNotes,
|
|
884
|
+
},
|
|
885
|
+
// Optionally update appointmentEndTime to the actual completion time
|
|
886
|
+
// appointmentEndTime: procedureCompletionTime,
|
|
887
|
+
updatedAt: serverTimestamp(),
|
|
736
888
|
};
|
|
737
|
-
|
|
738
889
|
return this.updateAppointment(appointmentId, updateData);
|
|
739
890
|
}
|
|
740
891
|
|
|
741
892
|
/**
|
|
742
|
-
*
|
|
743
|
-
*
|
|
744
|
-
* @param appointmentId ID of the appointment
|
|
745
|
-
* @returns The updated appointment
|
|
893
|
+
* Admin marks an appointment as No-Show.
|
|
746
894
|
*/
|
|
747
|
-
async
|
|
895
|
+
async markNoShowAdmin(appointmentId: string): Promise<Appointment> {
|
|
748
896
|
console.log(
|
|
749
|
-
`[APPOINTMENT_SERVICE]
|
|
897
|
+
`[APPOINTMENT_SERVICE] Admin marking no-show for: ${appointmentId}`
|
|
750
898
|
);
|
|
899
|
+
const appointment = await this.getAppointmentById(appointmentId);
|
|
900
|
+
if (!appointment)
|
|
901
|
+
throw new Error(`Appointment ${appointmentId} not found.`);
|
|
902
|
+
if (
|
|
903
|
+
Timestamp.now().toMillis() < appointment.appointmentStartTime.toMillis()
|
|
904
|
+
) {
|
|
905
|
+
throw new Error("Cannot mark no-show before appointment start time.");
|
|
906
|
+
}
|
|
751
907
|
return this.updateAppointmentStatus(
|
|
752
908
|
appointmentId,
|
|
753
|
-
AppointmentStatus.NO_SHOW
|
|
909
|
+
AppointmentStatus.NO_SHOW,
|
|
910
|
+
{
|
|
911
|
+
cancellationReason: "Patient did not show up for the appointment.",
|
|
912
|
+
canceledBy: "clinic",
|
|
913
|
+
}
|
|
754
914
|
);
|
|
755
915
|
}
|
|
756
916
|
|
|
917
|
+
/**
|
|
918
|
+
* Adds a media item to an appointment.
|
|
919
|
+
*/
|
|
920
|
+
async addMediaToAppointment(
|
|
921
|
+
appointmentId: string,
|
|
922
|
+
mediaItemData: Omit<AppointmentMediaItem, "id" | "uploadedAt">
|
|
923
|
+
): Promise<Appointment> {
|
|
924
|
+
console.log(
|
|
925
|
+
`[APPOINTMENT_SERVICE] Adding media to appointment ${appointmentId}`
|
|
926
|
+
);
|
|
927
|
+
const currentUser = this.auth.currentUser;
|
|
928
|
+
if (!currentUser) throw new Error("Authentication required.");
|
|
929
|
+
|
|
930
|
+
const newMediaItem: AppointmentMediaItem = {
|
|
931
|
+
...mediaItemData,
|
|
932
|
+
id: this.generateId(),
|
|
933
|
+
uploadedAt: Timestamp.now(),
|
|
934
|
+
uploadedBy: currentUser.uid,
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
const updateData: UpdateAppointmentData = {
|
|
938
|
+
media: arrayUnion(newMediaItem) as any,
|
|
939
|
+
updatedAt: serverTimestamp(),
|
|
940
|
+
};
|
|
941
|
+
return this.updateAppointment(appointmentId, updateData);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Removes a media item from an appointment.
|
|
946
|
+
*/
|
|
947
|
+
async removeMediaFromAppointment(
|
|
948
|
+
appointmentId: string,
|
|
949
|
+
mediaItemId: string
|
|
950
|
+
): Promise<Appointment> {
|
|
951
|
+
console.log(
|
|
952
|
+
`[APPOINTMENT_SERVICE] Removing media ${mediaItemId} from appointment ${appointmentId}`
|
|
953
|
+
);
|
|
954
|
+
const appointment = await this.getAppointmentById(appointmentId);
|
|
955
|
+
if (!appointment || !appointment.media) {
|
|
956
|
+
throw new Error("Appointment or media list not found.");
|
|
957
|
+
}
|
|
958
|
+
const mediaToRemove = appointment.media.find((m) => m.id === mediaItemId);
|
|
959
|
+
if (!mediaToRemove) {
|
|
960
|
+
throw new Error(`Media item ${mediaItemId} not found in appointment.`);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
const updateData: UpdateAppointmentData = {
|
|
964
|
+
media: arrayRemove(mediaToRemove) as any,
|
|
965
|
+
updatedAt: serverTimestamp(),
|
|
966
|
+
};
|
|
967
|
+
return this.updateAppointment(appointmentId, updateData);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Adds or updates review information for an appointment.
|
|
972
|
+
*/
|
|
973
|
+
async addReviewToAppointment(
|
|
974
|
+
appointmentId: string,
|
|
975
|
+
reviewData: Omit<PatientReviewInfo, "reviewedAt" | "reviewId">
|
|
976
|
+
): Promise<Appointment> {
|
|
977
|
+
console.log(
|
|
978
|
+
`[APPOINTMENT_SERVICE] Adding review to appointment ${appointmentId}`
|
|
979
|
+
);
|
|
980
|
+
const newReviewInfo: PatientReviewInfo = {
|
|
981
|
+
...reviewData,
|
|
982
|
+
reviewId: this.generateId(),
|
|
983
|
+
reviewedAt: Timestamp.now(),
|
|
984
|
+
};
|
|
985
|
+
const updateData: UpdateAppointmentData = {
|
|
986
|
+
reviewInfo: newReviewInfo,
|
|
987
|
+
updatedAt: serverTimestamp(),
|
|
988
|
+
};
|
|
989
|
+
return this.updateAppointment(appointmentId, updateData);
|
|
990
|
+
}
|
|
991
|
+
|
|
757
992
|
/**
|
|
758
993
|
* Updates the payment status of an appointment.
|
|
759
|
-
*
|
|
760
|
-
* @param appointmentId ID of the appointment
|
|
761
|
-
* @param paymentStatus New payment status
|
|
762
|
-
* @param paymentTransactionId Optional transaction ID for the payment
|
|
763
|
-
* @returns The updated appointment
|
|
764
994
|
*/
|
|
765
995
|
async updatePaymentStatus(
|
|
766
996
|
appointmentId: string,
|
|
@@ -770,12 +1000,11 @@ export class AppointmentService extends BaseService {
|
|
|
770
1000
|
console.log(
|
|
771
1001
|
`[APPOINTMENT_SERVICE] Updating payment status of appointment ${appointmentId} to ${paymentStatus}`
|
|
772
1002
|
);
|
|
773
|
-
|
|
774
1003
|
const updateData: UpdateAppointmentData = {
|
|
775
1004
|
paymentStatus,
|
|
776
1005
|
paymentTransactionId: paymentTransactionId || null,
|
|
1006
|
+
updatedAt: serverTimestamp(),
|
|
777
1007
|
};
|
|
778
|
-
|
|
779
1008
|
return this.updateAppointment(appointmentId, updateData);
|
|
780
1009
|
}
|
|
781
1010
|
|
|
@@ -197,4 +197,7 @@ export class ClinicAdminService extends BaseService {
|
|
|
197
197
|
this.getClinicService()
|
|
198
198
|
);
|
|
199
199
|
}
|
|
200
|
+
|
|
201
|
+
// TODO: Add more methods for clinic admins for managing permissions, editing profiles by the admin, or by the clinic group owner and so on
|
|
202
|
+
// Generally refactor admin permissions and clinic group permissions and systems for admin management
|
|
200
203
|
}
|
|
@@ -253,4 +253,12 @@ export class ClinicGroupService extends BaseService {
|
|
|
253
253
|
this.app
|
|
254
254
|
);
|
|
255
255
|
}
|
|
256
|
+
|
|
257
|
+
// TODO: Add a method to get all admin tokens for a clinic group (not just active ones)
|
|
258
|
+
|
|
259
|
+
// TODO: Refactor admin token methods not to add tokens to the clinicGroup document,
|
|
260
|
+
// but to add them to a subcollection called adminTokens that belongs to a specific clinicGroup document
|
|
261
|
+
|
|
262
|
+
// TODO: Add granular control over admin permissions, e.g. only allow admins to manage certain clinics to tokens directly
|
|
263
|
+
// TODO: Generally refactor admin tokens and invites, also create cloud function to send invites and send updates when sombody uses the token
|
|
256
264
|
}
|
|
@@ -23,10 +23,7 @@ import {
|
|
|
23
23
|
DocumentTemplate,
|
|
24
24
|
UpdateDocumentTemplateData,
|
|
25
25
|
} from "../../types";
|
|
26
|
-
import {
|
|
27
|
-
FILLED_DOCUMENTS_COLLECTION,
|
|
28
|
-
DOCUMENTATION_TEMPLATES_COLLECTION,
|
|
29
|
-
} from "../../types";
|
|
26
|
+
import { DOCUMENTATION_TEMPLATES_COLLECTION } from "../../types";
|
|
30
27
|
import {
|
|
31
28
|
createDocumentTemplateSchema,
|
|
32
29
|
updateDocumentTemplateSchema,
|
|
@@ -76,6 +73,9 @@ export class DocumentationTemplateService extends BaseService {
|
|
|
76
73
|
version: 1,
|
|
77
74
|
isActive: true,
|
|
78
75
|
tags: validatedData.tags || [],
|
|
76
|
+
isUserForm: validatedData.isUserForm || false,
|
|
77
|
+
isRequired: validatedData.isRequired || false,
|
|
78
|
+
sortingOrder: validatedData.sortingOrder || 0,
|
|
79
79
|
};
|
|
80
80
|
|
|
81
81
|
// Save to Firestore
|
|
@@ -123,30 +123,38 @@ export class DocumentationTemplateService extends BaseService {
|
|
|
123
123
|
// Process elements if provided
|
|
124
124
|
let updatedElements = template.elements;
|
|
125
125
|
if (validatedData.elements) {
|
|
126
|
-
// Generate IDs for new elements
|
|
127
126
|
updatedElements = validatedData.elements.map((element) => ({
|
|
128
127
|
...element,
|
|
129
|
-
id: this.generateId(),
|
|
128
|
+
id: (element as any).id || this.generateId(),
|
|
130
129
|
})) as DocumentElement[];
|
|
131
130
|
}
|
|
132
131
|
|
|
133
|
-
|
|
134
|
-
const updateData: Partial<DocumentTemplate> = {
|
|
135
|
-
...validatedData,
|
|
132
|
+
const updatePayload: Partial<DocumentTemplate> = {
|
|
136
133
|
elements: updatedElements,
|
|
137
134
|
updatedAt: Date.now(),
|
|
138
135
|
version: template.version + 1,
|
|
139
136
|
};
|
|
140
137
|
|
|
141
|
-
//
|
|
138
|
+
// Conditionally add fields from validatedData to avoid overwriting with undefined
|
|
139
|
+
if (validatedData.title !== undefined)
|
|
140
|
+
updatePayload.title = validatedData.title;
|
|
141
|
+
if (validatedData.description !== undefined)
|
|
142
|
+
updatePayload.description = validatedData.description;
|
|
143
|
+
if (validatedData.isActive !== undefined)
|
|
144
|
+
updatePayload.isActive = validatedData.isActive;
|
|
145
|
+
if (validatedData.tags !== undefined)
|
|
146
|
+
updatePayload.tags = validatedData.tags;
|
|
147
|
+
if (validatedData.isUserForm !== undefined)
|
|
148
|
+
updatePayload.isUserForm = validatedData.isUserForm;
|
|
149
|
+
if (validatedData.isRequired !== undefined)
|
|
150
|
+
updatePayload.isRequired = validatedData.isRequired;
|
|
151
|
+
if (validatedData.sortingOrder !== undefined)
|
|
152
|
+
updatePayload.sortingOrder = validatedData.sortingOrder;
|
|
153
|
+
|
|
142
154
|
const docRef = doc(this.collectionRef, templateId);
|
|
143
|
-
await updateDoc(docRef,
|
|
155
|
+
await updateDoc(docRef, updatePayload);
|
|
144
156
|
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
...template,
|
|
148
|
-
...updateData,
|
|
149
|
-
} as DocumentTemplate;
|
|
157
|
+
return { ...template, ...updatePayload } as DocumentTemplate;
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
/**
|