@blackcode_sa/metaestetics-api 1.7.22 → 1.7.24
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/index.d.mts +200 -2
- package/dist/index.d.ts +200 -2
- package/dist/index.js +1036 -577
- package/dist/index.mjs +1167 -697
- package/package.json +1 -1
- package/src/index.ts +8 -0
- package/src/services/appointment/appointment.service.ts +72 -10
- package/src/services/clinic/README.md +117 -0
- package/src/services/clinic/practitioner-invite.service.ts +519 -0
- package/src/types/clinic/index.ts +3 -0
- package/src/types/clinic/practitioner-invite.types.ts +91 -0
- package/src/validations/appointment.schema.ts +33 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { ProcedureService } from "./services/procedure/procedure.service";
|
|
|
25
25
|
export { ClinicService } from "./services/clinic/clinic.service";
|
|
26
26
|
export { ClinicAdminService } from "./services/clinic/clinic-admin.service";
|
|
27
27
|
export { ClinicGroupService } from "./services/clinic/clinic-group.service";
|
|
28
|
+
export { PractitionerInviteService } from "./services/clinic/practitioner-invite.service";
|
|
28
29
|
export {
|
|
29
30
|
DocumentationTemplateService,
|
|
30
31
|
FilledDocumentService,
|
|
@@ -197,17 +198,24 @@ export type {
|
|
|
197
198
|
CreateDefaultClinicGroupData,
|
|
198
199
|
ClinicGroupSetupData,
|
|
199
200
|
ClinicBranchSetupData,
|
|
201
|
+
PractitionerInvite,
|
|
202
|
+
CreatePractitionerInviteData,
|
|
203
|
+
UpdatePractitionerInviteData,
|
|
204
|
+
PractitionerInviteFilters,
|
|
205
|
+
ProposedWorkingHours,
|
|
200
206
|
} from "./types/clinic";
|
|
201
207
|
export {
|
|
202
208
|
CLINICS_COLLECTION,
|
|
203
209
|
CLINIC_GROUPS_COLLECTION,
|
|
204
210
|
CLINIC_ADMINS_COLLECTION,
|
|
211
|
+
PRACTITIONER_INVITES_COLLECTION,
|
|
205
212
|
PracticeType,
|
|
206
213
|
Language,
|
|
207
214
|
ClinicTag,
|
|
208
215
|
ClinicPhotoTag,
|
|
209
216
|
AdminTokenStatus,
|
|
210
217
|
SubscriptionModel,
|
|
218
|
+
PractitionerInviteStatus,
|
|
211
219
|
} from "./types/clinic";
|
|
212
220
|
|
|
213
221
|
// Profile info types
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
createAppointmentSchema,
|
|
36
36
|
updateAppointmentSchema,
|
|
37
37
|
searchAppointmentsSchema,
|
|
38
|
+
rescheduleAppointmentSchema,
|
|
38
39
|
} from "../../validations/appointment.schema";
|
|
39
40
|
|
|
40
41
|
// Import other services needed (dependency injection pattern)
|
|
@@ -682,26 +683,87 @@ export class AppointmentService extends BaseService {
|
|
|
682
683
|
* Admin proposes to reschedule an appointment.
|
|
683
684
|
* Sets status to RESCHEDULED_BY_CLINIC and updates times.
|
|
684
685
|
*/
|
|
685
|
-
async rescheduleAppointmentAdmin(
|
|
686
|
-
appointmentId: string
|
|
687
|
-
newStartTime: Timestamp,
|
|
688
|
-
newEndTime: Timestamp
|
|
689
|
-
): Promise<Appointment> {
|
|
686
|
+
async rescheduleAppointmentAdmin(params: {
|
|
687
|
+
appointmentId: string;
|
|
688
|
+
newStartTime: any; // Accept any type (number, string, Timestamp, etc.)
|
|
689
|
+
newEndTime: any; // Accept any type (number, string, Timestamp, etc.)
|
|
690
|
+
}): Promise<Appointment> {
|
|
690
691
|
console.log(
|
|
691
|
-
`[APPOINTMENT_SERVICE] Admin rescheduling appointment: ${appointmentId}`
|
|
692
|
+
`[APPOINTMENT_SERVICE] Admin rescheduling appointment: ${params.appointmentId}`
|
|
693
|
+
);
|
|
694
|
+
|
|
695
|
+
// Validate input data
|
|
696
|
+
const validatedParams = await rescheduleAppointmentSchema.parseAsync(
|
|
697
|
+
params
|
|
692
698
|
);
|
|
693
|
-
|
|
699
|
+
|
|
700
|
+
// Convert input to Timestamp objects
|
|
701
|
+
const startTimestamp = this.convertToTimestamp(
|
|
702
|
+
validatedParams.newStartTime
|
|
703
|
+
);
|
|
704
|
+
const endTimestamp = this.convertToTimestamp(validatedParams.newEndTime);
|
|
705
|
+
|
|
706
|
+
if (endTimestamp.toMillis() <= startTimestamp.toMillis()) {
|
|
694
707
|
throw new Error("New end time must be after new start time.");
|
|
695
708
|
}
|
|
709
|
+
|
|
696
710
|
const updateData: UpdateAppointmentData = {
|
|
697
711
|
status: AppointmentStatus.RESCHEDULED_BY_CLINIC,
|
|
698
|
-
appointmentStartTime:
|
|
699
|
-
appointmentEndTime:
|
|
712
|
+
appointmentStartTime: startTimestamp,
|
|
713
|
+
appointmentEndTime: endTimestamp,
|
|
700
714
|
rescheduleTime: Timestamp.now(),
|
|
701
715
|
confirmationTime: null,
|
|
702
716
|
updatedAt: serverTimestamp(),
|
|
703
717
|
};
|
|
704
|
-
return this.updateAppointment(appointmentId, updateData);
|
|
718
|
+
return this.updateAppointment(validatedParams.appointmentId, updateData);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Helper method to convert various timestamp formats to Firestore Timestamp
|
|
723
|
+
* @param value - Any timestamp format (Timestamp, number, string, Date, serialized Timestamp)
|
|
724
|
+
* @returns Firestore Timestamp object
|
|
725
|
+
*/
|
|
726
|
+
private convertToTimestamp(value: any): Timestamp {
|
|
727
|
+
console.log(`[APPOINTMENT_SERVICE] Converting timestamp:`, {
|
|
728
|
+
value,
|
|
729
|
+
type: typeof value,
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
// If it's already a Timestamp object with methods
|
|
733
|
+
if (value && typeof value.toMillis === "function") {
|
|
734
|
+
return value;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// If it's a number (milliseconds since epoch)
|
|
738
|
+
if (typeof value === "number") {
|
|
739
|
+
return Timestamp.fromMillis(value);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// If it's a string (ISO date string)
|
|
743
|
+
if (typeof value === "string") {
|
|
744
|
+
return Timestamp.fromDate(new Date(value));
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// If it's a Date object
|
|
748
|
+
if (value instanceof Date) {
|
|
749
|
+
return Timestamp.fromDate(value);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// If it has _seconds property (serialized Timestamp) - THIS IS WHAT FRONTEND SENDS
|
|
753
|
+
if (value && typeof value._seconds === "number") {
|
|
754
|
+
return new Timestamp(value._seconds, value._nanoseconds || 0);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// If it has seconds property (serialized Timestamp)
|
|
758
|
+
if (value && typeof value.seconds === "number") {
|
|
759
|
+
return new Timestamp(value.seconds, value.nanoseconds || 0);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
throw new Error(
|
|
763
|
+
`Invalid timestamp format: ${typeof value}, value: ${JSON.stringify(
|
|
764
|
+
value
|
|
765
|
+
)}`
|
|
766
|
+
);
|
|
705
767
|
}
|
|
706
768
|
|
|
707
769
|
/**
|
|
@@ -85,3 +85,120 @@ Initializes the service with Firestore, Auth, App instances, and required depend
|
|
|
85
85
|
- **`photos.utils.ts`**: Firebase Storage interactions (`uploadPhoto`, `uploadMultiplePhotos`, `deletePhoto`).
|
|
86
86
|
- **`admin.utils.ts`**: Helpers for clinic admin interactions (used by `ClinicAdminService`).
|
|
87
87
|
- **`search.utils.ts`**: Geo-radius search logic (`findClinicsInRadius`).
|
|
88
|
+
|
|
89
|
+
# Clinic Services
|
|
90
|
+
|
|
91
|
+
This directory contains services related to clinic operations.
|
|
92
|
+
|
|
93
|
+
## Services
|
|
94
|
+
|
|
95
|
+
### ClinicService
|
|
96
|
+
|
|
97
|
+
Handles clinic CRUD operations, searching, and management.
|
|
98
|
+
|
|
99
|
+
### ClinicGroupService
|
|
100
|
+
|
|
101
|
+
Manages clinic groups and their operations.
|
|
102
|
+
|
|
103
|
+
### ClinicAdminService
|
|
104
|
+
|
|
105
|
+
Handles clinic administrator operations.
|
|
106
|
+
|
|
107
|
+
### PractitionerInviteService
|
|
108
|
+
|
|
109
|
+
Manages the practitioner invitation system for clinics.
|
|
110
|
+
|
|
111
|
+
## PractitionerInviteService
|
|
112
|
+
|
|
113
|
+
The `PractitionerInviteService` handles the complete flow of inviting practitioners to join clinics.
|
|
114
|
+
|
|
115
|
+
### Flow
|
|
116
|
+
|
|
117
|
+
1. Clinic searches for active practitioners by name or email (using existing practitioner search services)
|
|
118
|
+
2. Clinic creates an invite request with proposed working hours
|
|
119
|
+
3. Practitioner receives the invite and can accept or reject it
|
|
120
|
+
4. Admins can cancel pending invites if needed
|
|
121
|
+
5. Connection between practitioner and clinic is handled by aggregation side-effects
|
|
122
|
+
|
|
123
|
+
### Methods
|
|
124
|
+
|
|
125
|
+
#### Core Invite Methods
|
|
126
|
+
|
|
127
|
+
- `createInviteAdmin(practitionerId, clinicId, proposedWorkingHours, invitedBy, message?)` - Create a new invite
|
|
128
|
+
- `acceptInviteDoctor(inviteId)` - Doctor accepts an invite
|
|
129
|
+
- `rejectInviteDoctor(inviteId, rejectionReason?)` - Doctor rejects an invite
|
|
130
|
+
- `cancelInviteAdmin(inviteId, cancelReason?)` - Admin cancels a pending invite
|
|
131
|
+
|
|
132
|
+
#### Retrieval Methods
|
|
133
|
+
|
|
134
|
+
- `getAllInvitesDoctor(practitionerId, statusFilter?)` - Get all invites for a practitioner
|
|
135
|
+
- `getAllInvitesClinic(clinicId, statusFilter?)` - Get all invites for a clinic
|
|
136
|
+
- `getInviteById(inviteId)` - Get a specific invite
|
|
137
|
+
- `getInvitesWithFilters(filters)` - Advanced filtering
|
|
138
|
+
|
|
139
|
+
#### Admin Methods
|
|
140
|
+
|
|
141
|
+
- `deleteInvite(inviteId)` - Delete an invite (admin only)
|
|
142
|
+
|
|
143
|
+
### Status Filtering
|
|
144
|
+
|
|
145
|
+
All retrieval methods support filtering by invite status:
|
|
146
|
+
|
|
147
|
+
- `PENDING` - Invite has been sent but not responded to
|
|
148
|
+
- `ACCEPTED` - Practitioner has accepted the invite
|
|
149
|
+
- `REJECTED` - Practitioner has rejected the invite
|
|
150
|
+
- `CANCELLED` - Admin has cancelled the invite
|
|
151
|
+
|
|
152
|
+
### Usage Example
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Create service instance
|
|
156
|
+
const practitionerInviteService = new PractitionerInviteService(db, auth, app);
|
|
157
|
+
|
|
158
|
+
// Create an invite
|
|
159
|
+
const invite = await practitionerInviteService.createInviteAdmin(
|
|
160
|
+
"practitioner123",
|
|
161
|
+
"clinic456",
|
|
162
|
+
proposedHours,
|
|
163
|
+
"admin789",
|
|
164
|
+
"We'd love to have you join our team!"
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Get clinic's invites
|
|
168
|
+
const clinicInvites = await practitionerInviteService.getAllInvitesClinic(
|
|
169
|
+
"clinic456",
|
|
170
|
+
[PractitionerInviteStatus.PENDING]
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Doctor accepts invite
|
|
174
|
+
const acceptedInvite = await practitionerInviteService.acceptInviteDoctor(
|
|
175
|
+
invite.id
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// Admin cancels pending invite
|
|
179
|
+
const cancelledInvite = await practitionerInviteService.cancelInviteAdmin(
|
|
180
|
+
invite.id,
|
|
181
|
+
"Position no longer available"
|
|
182
|
+
);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Data Structure
|
|
186
|
+
|
|
187
|
+
The service uses existing aggregation types from the profile module:
|
|
188
|
+
|
|
189
|
+
- `PractitionerProfileInfo` - Complete practitioner information including certification
|
|
190
|
+
- `ClinicInfo` - Complete clinic information including location and contact details
|
|
191
|
+
|
|
192
|
+
### Error Handling
|
|
193
|
+
|
|
194
|
+
All methods include proper error handling and logging. Common errors:
|
|
195
|
+
|
|
196
|
+
- Practitioner/Clinic not found
|
|
197
|
+
- Duplicate pending invites
|
|
198
|
+
- Invalid status transitions (only pending invites can be accepted/rejected/cancelled)
|
|
199
|
+
- Permission issues
|
|
200
|
+
|
|
201
|
+
### Firestore Collections
|
|
202
|
+
|
|
203
|
+
- Collection: `practitioner-invites`
|
|
204
|
+
- Documents contain complete invite information with embedded practitioner and clinic details
|