@blackcode_sa/metaestetics-api 1.7.23 → 1.7.25
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.js +75 -37
- package/dist/admin/index.mjs +75 -37
- package/dist/index.d.mts +189 -1
- package/dist/index.d.ts +189 -1
- package/dist/index.js +983 -576
- package/dist/index.mjs +1110 -692
- package/package.json +1 -1
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +106 -45
- package/src/index.ts +8 -0
- 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/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import * as admin from "firebase-admin";
|
|
2
2
|
import { ClinicInfo } from "../../../types/profile"; // Corrected path
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
PRACTITIONERS_COLLECTION,
|
|
5
|
+
Practitioner,
|
|
6
|
+
} from "../../../types/practitioner"; // Corrected path
|
|
4
7
|
import { PROCEDURES_COLLECTION } from "../../../types/procedure"; // Added procedure collection
|
|
5
|
-
import { CLINIC_GROUPS_COLLECTION } from "../../../types/clinic"; // Added clinic group collection
|
|
8
|
+
import { CLINIC_GROUPS_COLLECTION, ClinicGroup } from "../../../types/clinic"; // Added clinic group collection
|
|
6
9
|
import { ClinicLocation } from "../../../types/clinic"; // Added ClinicLocation type
|
|
7
10
|
import { CALENDAR_COLLECTION } from "../../../types/calendar"; // Added calendar collection
|
|
8
11
|
import { PATIENTS_COLLECTION } from "../../../types/patient"; // Added patient collection
|
|
@@ -103,16 +106,27 @@ export class ClinicAggregationService {
|
|
|
103
106
|
.collection(PRACTITIONERS_COLLECTION)
|
|
104
107
|
.doc(practitionerId);
|
|
105
108
|
|
|
106
|
-
// Remove old clinic info based on ID matcher
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
// Remove old clinic info based on ID matcher and add updated info
|
|
110
|
+
// First, get the current practitioner data to filter clinicsInfo manually
|
|
111
|
+
const practitionerDoc = await practitionerRef.get();
|
|
112
|
+
if (practitionerDoc.exists) {
|
|
113
|
+
const practitionerData = practitionerDoc.data() as Practitioner;
|
|
114
|
+
const currentClinicsInfo = practitionerData?.clinicsInfo || [];
|
|
115
|
+
|
|
116
|
+
// Filter out the clinic info with matching ID
|
|
117
|
+
const filteredClinicsInfo = currentClinicsInfo.filter(
|
|
118
|
+
(clinic: any) => clinic.id !== clinicId
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Add the updated clinic info to the filtered array
|
|
122
|
+
const updatedClinicsInfo = [...filteredClinicsInfo, clinicInfo];
|
|
123
|
+
|
|
124
|
+
// Update with the complete new array
|
|
125
|
+
batch.update(practitionerRef, {
|
|
126
|
+
clinicsInfo: updatedClinicsInfo,
|
|
127
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
try {
|
|
@@ -194,7 +208,6 @@ export class ClinicAggregationService {
|
|
|
194
208
|
return;
|
|
195
209
|
}
|
|
196
210
|
|
|
197
|
-
const batch = this.db.batch();
|
|
198
211
|
const clinicId = clinicInfo.id;
|
|
199
212
|
const groupRef = this.db
|
|
200
213
|
.collection(CLINIC_GROUPS_COLLECTION)
|
|
@@ -204,25 +217,38 @@ export class ClinicAggregationService {
|
|
|
204
217
|
`[ClinicAggregationService] Starting update of ClinicInfo (ID: ${clinicId}) in Clinic Group ${clinicGroupId}.`
|
|
205
218
|
);
|
|
206
219
|
|
|
207
|
-
// Remove old clinic info based on ID matcher
|
|
208
|
-
batch.update(groupRef, {
|
|
209
|
-
clinicsInfo: admin.firestore.FieldValue.arrayRemove({ id: clinicId }),
|
|
210
|
-
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
211
|
-
});
|
|
212
|
-
// Add updated clinic info
|
|
213
|
-
batch.update(groupRef, {
|
|
214
|
-
clinicsInfo: admin.firestore.FieldValue.arrayUnion(clinicInfo),
|
|
215
|
-
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
216
|
-
});
|
|
217
|
-
|
|
218
220
|
try {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
221
|
+
// Get current clinic group data to filter clinicsInfo manually
|
|
222
|
+
const clinicGroupDoc = await groupRef.get();
|
|
223
|
+
if (clinicGroupDoc.exists) {
|
|
224
|
+
const clinicGroupData = clinicGroupDoc.data() as ClinicGroup;
|
|
225
|
+
const currentClinicsInfo = clinicGroupData?.clinicsInfo || [];
|
|
226
|
+
|
|
227
|
+
// Filter out the clinic info with matching ID
|
|
228
|
+
const filteredClinicsInfo = currentClinicsInfo.filter(
|
|
229
|
+
(clinic: any) => clinic.id !== clinicId
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
// Add the updated clinic info to the filtered array
|
|
233
|
+
const updatedClinicsInfo = [...filteredClinicsInfo, clinicInfo];
|
|
234
|
+
|
|
235
|
+
// Update with the complete new array
|
|
236
|
+
await groupRef.update({
|
|
237
|
+
clinicsInfo: updatedClinicsInfo,
|
|
238
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
console.log(
|
|
242
|
+
`[ClinicAggregationService] Successfully updated ClinicInfo (ID: ${clinicId}) in Clinic Group ${clinicGroupId}.`
|
|
243
|
+
);
|
|
244
|
+
} else {
|
|
245
|
+
console.warn(
|
|
246
|
+
`[ClinicAggregationService] Clinic Group ${clinicGroupId} not found. Cannot update clinic info.`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
223
249
|
} catch (error) {
|
|
224
250
|
console.error(
|
|
225
|
-
`[ClinicAggregationService] Error
|
|
251
|
+
`[ClinicAggregationService] Error updating ClinicInfo (ID: ${clinicId}) in Clinic Group ${clinicGroupId}:`,
|
|
226
252
|
error
|
|
227
253
|
);
|
|
228
254
|
throw error;
|
|
@@ -427,13 +453,28 @@ export class ClinicAggregationService {
|
|
|
427
453
|
.collection(PRACTITIONERS_COLLECTION)
|
|
428
454
|
.doc(practitionerId);
|
|
429
455
|
|
|
430
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
456
|
+
// Get current practitioner data to filter manually
|
|
457
|
+
const practitionerDoc = await practitionerRef.get();
|
|
458
|
+
if (practitionerDoc.exists) {
|
|
459
|
+
const practitionerData = practitionerDoc.data() as Practitioner;
|
|
460
|
+
const currentClinicIds = practitionerData?.clinics || [];
|
|
461
|
+
const currentClinicsInfo = practitionerData?.clinicsInfo || [];
|
|
462
|
+
|
|
463
|
+
// Filter out the clinic ID and clinic info with matching ID
|
|
464
|
+
const filteredClinicIds = currentClinicIds.filter(
|
|
465
|
+
(id: string) => id !== clinicId
|
|
466
|
+
);
|
|
467
|
+
const filteredClinicsInfo = currentClinicsInfo.filter(
|
|
468
|
+
(clinic: any) => clinic.id !== clinicId
|
|
469
|
+
);
|
|
470
|
+
|
|
471
|
+
// Update with the filtered arrays
|
|
472
|
+
batch.update(practitionerRef, {
|
|
473
|
+
clinics: filteredClinicIds,
|
|
474
|
+
clinicsInfo: filteredClinicsInfo,
|
|
475
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
437
478
|
}
|
|
438
479
|
|
|
439
480
|
try {
|
|
@@ -518,16 +559,36 @@ export class ClinicAggregationService {
|
|
|
518
559
|
);
|
|
519
560
|
|
|
520
561
|
try {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
562
|
+
// Get current clinic group data to filter manually
|
|
563
|
+
const clinicGroupDoc = await groupRef.get();
|
|
564
|
+
if (clinicGroupDoc.exists) {
|
|
565
|
+
const clinicGroupData = clinicGroupDoc.data() as ClinicGroup;
|
|
566
|
+
const currentClinicIds = clinicGroupData?.clinics || [];
|
|
567
|
+
const currentClinicsInfo = clinicGroupData?.clinicsInfo || [];
|
|
568
|
+
|
|
569
|
+
// Filter out the clinic ID and clinic info with matching ID
|
|
570
|
+
const filteredClinicIds = currentClinicIds.filter(
|
|
571
|
+
(id: string) => id !== clinicId
|
|
572
|
+
);
|
|
573
|
+
const filteredClinicsInfo = currentClinicsInfo.filter(
|
|
574
|
+
(clinic: any) => clinic.id !== clinicId
|
|
575
|
+
);
|
|
527
576
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
577
|
+
// Update with the filtered arrays
|
|
578
|
+
await groupRef.update({
|
|
579
|
+
clinics: filteredClinicIds,
|
|
580
|
+
clinicsInfo: filteredClinicsInfo,
|
|
581
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
console.log(
|
|
585
|
+
`[ClinicAggregationService] Successfully removed Clinic (ID: ${clinicId}) from Clinic Group ${clinicGroupId}.`
|
|
586
|
+
);
|
|
587
|
+
} else {
|
|
588
|
+
console.warn(
|
|
589
|
+
`[ClinicAggregationService] Clinic Group ${clinicGroupId} not found. Cannot remove clinic.`
|
|
590
|
+
);
|
|
591
|
+
}
|
|
531
592
|
} catch (error) {
|
|
532
593
|
console.error(
|
|
533
594
|
`[ClinicAggregationService] Error removing Clinic (ID: ${clinicId}) from Clinic Group ${clinicGroupId}:`,
|
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
|
|
@@ -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
|