@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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.7.23",
4
+ "version": "1.7.25",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -1,8 +1,11 @@
1
1
  import * as admin from "firebase-admin";
2
2
  import { ClinicInfo } from "../../../types/profile"; // Corrected path
3
- import { PRACTITIONERS_COLLECTION } from "../../../types/practitioner"; // Corrected path
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
- batch.update(practitionerRef, {
108
- clinicsInfo: admin.firestore.FieldValue.arrayRemove({ id: clinicId }),
109
- updatedAt: admin.firestore.FieldValue.serverTimestamp(),
110
- });
111
- // Add updated clinic info
112
- batch.update(practitionerRef, {
113
- clinicsInfo: admin.firestore.FieldValue.arrayUnion(clinicInfo),
114
- updatedAt: admin.firestore.FieldValue.serverTimestamp(),
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
- await batch.commit();
220
- console.log(
221
- `[ClinicAggregationService] Successfully updated ClinicInfo (ID: ${clinicId}) in Clinic Group ${clinicGroupId}.`
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 committing update for ClinicInfo (ID: ${clinicId}) in Clinic Group ${clinicGroupId}:`,
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
- // Remove clinic ID from clinicIds array
431
- batch.update(practitionerRef, {
432
- clinicIds: admin.firestore.FieldValue.arrayRemove(clinicId),
433
- // Remove all clinic info objects where id matches the clinic ID
434
- clinicsInfo: admin.firestore.FieldValue.arrayRemove({ id: clinicId }),
435
- updatedAt: admin.firestore.FieldValue.serverTimestamp(),
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
- await groupRef.update({
522
- clinicIds: admin.firestore.FieldValue.arrayRemove(clinicId),
523
- // Remove all clinic info objects where id matches the clinic ID
524
- clinicsInfo: admin.firestore.FieldValue.arrayRemove({ id: clinicId }),
525
- updatedAt: admin.firestore.FieldValue.serverTimestamp(),
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
- console.log(
529
- `[ClinicAggregationService] Successfully removed Clinic (ID: ${clinicId}) from Clinic Group ${clinicGroupId}.`
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