@blackcode_sa/metaestetics-api 1.5.28 → 1.5.30
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 +1324 -1
- package/dist/admin/index.d.ts +1324 -1
- package/dist/admin/index.js +1674 -2
- package/dist/admin/index.mjs +1668 -2
- package/dist/backoffice/index.d.mts +99 -7
- package/dist/backoffice/index.d.ts +99 -7
- package/dist/index.d.mts +4036 -2372
- package/dist/index.d.ts +4036 -2372
- package/dist/index.js +2331 -2009
- package/dist/index.mjs +2279 -1954
- package/package.json +2 -1
- package/src/admin/aggregation/README.md +79 -0
- package/src/admin/aggregation/clinic/README.md +52 -0
- package/src/admin/aggregation/clinic/clinic.aggregation.service.ts +642 -0
- package/src/admin/aggregation/patient/README.md +27 -0
- package/src/admin/aggregation/patient/patient.aggregation.service.ts +141 -0
- package/src/admin/aggregation/practitioner/README.md +42 -0
- package/src/admin/aggregation/practitioner/practitioner.aggregation.service.ts +433 -0
- package/src/admin/aggregation/procedure/README.md +43 -0
- package/src/admin/aggregation/procedure/procedure.aggregation.service.ts +508 -0
- package/src/admin/index.ts +60 -4
- package/src/admin/mailing/README.md +95 -0
- package/src/admin/mailing/base.mailing.service.ts +131 -0
- package/src/admin/mailing/index.ts +2 -0
- package/src/admin/mailing/practitionerInvite/index.ts +1 -0
- package/src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts +256 -0
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +101 -0
- package/src/index.ts +28 -4
- package/src/services/README.md +106 -0
- package/src/services/clinic/README.md +87 -0
- package/src/services/clinic/clinic.service.ts +197 -107
- package/src/services/clinic/utils/clinic.utils.ts +68 -119
- package/src/services/clinic/utils/filter.utils.d.ts +23 -0
- package/src/services/clinic/utils/filter.utils.ts +264 -0
- package/src/services/practitioner/README.md +145 -0
- package/src/services/practitioner/practitioner.service.ts +439 -104
- package/src/services/procedure/README.md +88 -0
- package/src/services/procedure/procedure.service.ts +521 -311
- package/src/services/reviews/reviews.service.ts +842 -0
- package/src/types/clinic/index.ts +24 -56
- package/src/types/practitioner/index.ts +34 -33
- package/src/types/procedure/index.ts +32 -0
- package/src/types/profile/index.ts +1 -1
- package/src/types/reviews/index.ts +126 -0
- package/src/validations/clinic.schema.ts +37 -64
- package/src/validations/practitioner.schema.ts +42 -32
- package/src/validations/procedure.schema.ts +11 -3
- package/src/validations/reviews.schema.ts +189 -0
- package/src/services/clinic/utils/review.utils.ts +0 -93
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
import * as admin from "firebase-admin";
|
|
2
|
+
import { PRACTITIONERS_COLLECTION } from "../../../types/practitioner";
|
|
3
|
+
import { PROCEDURES_COLLECTION } from "../../../types/procedure";
|
|
4
|
+
import { CLINICS_COLLECTION } from "../../../types/clinic";
|
|
5
|
+
import { ProcedureSummaryInfo } from "../../../types/procedure";
|
|
6
|
+
|
|
7
|
+
const CALENDAR_SUBCOLLECTION_ID = "calendar";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @class ProcedureAggregationService
|
|
11
|
+
* @description Handles aggregation tasks related to procedure data updates/deletions.
|
|
12
|
+
*/
|
|
13
|
+
export class ProcedureAggregationService {
|
|
14
|
+
private db: admin.firestore.Firestore;
|
|
15
|
+
|
|
16
|
+
constructor(firestore?: admin.firestore.Firestore) {
|
|
17
|
+
this.db = firestore || admin.firestore();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Adds procedure information to a practitioner when a new procedure is created
|
|
22
|
+
* @param practitionerId - ID of the practitioner who performs the procedure
|
|
23
|
+
* @param procedureSummary - Summary information about the procedure
|
|
24
|
+
* @returns {Promise<void>}
|
|
25
|
+
*/
|
|
26
|
+
async addProcedureToPractitioner(
|
|
27
|
+
practitionerId: string,
|
|
28
|
+
procedureSummary: ProcedureSummaryInfo
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
if (!practitionerId || !procedureSummary) {
|
|
31
|
+
console.log(
|
|
32
|
+
"[ProcedureAggregationService] Missing practitionerId or procedureSummary for adding procedure to practitioner. Skipping."
|
|
33
|
+
);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const procedureId = procedureSummary.id;
|
|
38
|
+
console.log(
|
|
39
|
+
`[ProcedureAggregationService] Adding procedure ${procedureId} to practitioner ${practitionerId}.`
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const practitionerRef = this.db
|
|
43
|
+
.collection(PRACTITIONERS_COLLECTION)
|
|
44
|
+
.doc(practitionerId);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await practitionerRef.update({
|
|
48
|
+
procedureIds: admin.firestore.FieldValue.arrayUnion(procedureId),
|
|
49
|
+
proceduresInfo: admin.firestore.FieldValue.arrayUnion(procedureSummary),
|
|
50
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(
|
|
54
|
+
`[ProcedureAggregationService] Successfully added procedure ${procedureId} to practitioner ${practitionerId}.`
|
|
55
|
+
);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(
|
|
58
|
+
`[ProcedureAggregationService] Error adding procedure ${procedureId} to practitioner ${practitionerId}:`,
|
|
59
|
+
error
|
|
60
|
+
);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Adds procedure information to a clinic when a new procedure is created
|
|
67
|
+
* @param clinicId - ID of the clinic where the procedure is performed
|
|
68
|
+
* @param procedureSummary - Summary information about the procedure
|
|
69
|
+
* @returns {Promise<void>}
|
|
70
|
+
*/
|
|
71
|
+
async addProcedureToClinic(
|
|
72
|
+
clinicId: string,
|
|
73
|
+
procedureSummary: ProcedureSummaryInfo
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
if (!clinicId || !procedureSummary) {
|
|
76
|
+
console.log(
|
|
77
|
+
"[ProcedureAggregationService] Missing clinicId or procedureSummary for adding procedure to clinic. Skipping."
|
|
78
|
+
);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const procedureId = procedureSummary.id;
|
|
83
|
+
console.log(
|
|
84
|
+
`[ProcedureAggregationService] Adding procedure ${procedureId} to clinic ${clinicId}.`
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await clinicRef.update({
|
|
91
|
+
procedures: admin.firestore.FieldValue.arrayUnion(procedureId),
|
|
92
|
+
proceduresInfo: admin.firestore.FieldValue.arrayUnion(procedureSummary),
|
|
93
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
console.log(
|
|
97
|
+
`[ProcedureAggregationService] Successfully added procedure ${procedureId} to clinic ${clinicId}.`
|
|
98
|
+
);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error(
|
|
101
|
+
`[ProcedureAggregationService] Error adding procedure ${procedureId} to clinic ${clinicId}:`,
|
|
102
|
+
error
|
|
103
|
+
);
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Updates procedure information in a practitioner document
|
|
110
|
+
* @param practitionerId - ID of the practitioner who performs the procedure
|
|
111
|
+
* @param procedureSummary - Updated summary information about the procedure
|
|
112
|
+
* @returns {Promise<void>}
|
|
113
|
+
*/
|
|
114
|
+
async updateProcedureInfoInPractitioner(
|
|
115
|
+
practitionerId: string,
|
|
116
|
+
procedureSummary: ProcedureSummaryInfo
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
if (!practitionerId || !procedureSummary) {
|
|
119
|
+
console.log(
|
|
120
|
+
"[ProcedureAggregationService] Missing practitionerId or procedureSummary for updating procedure in practitioner. Skipping."
|
|
121
|
+
);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const procedureId = procedureSummary.id;
|
|
126
|
+
console.log(
|
|
127
|
+
`[ProcedureAggregationService] Updating procedure ${procedureId} info in practitioner ${practitionerId}.`
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const practitionerRef = this.db
|
|
131
|
+
.collection(PRACTITIONERS_COLLECTION)
|
|
132
|
+
.doc(practitionerId);
|
|
133
|
+
|
|
134
|
+
// This requires a transaction to avoid race conditions
|
|
135
|
+
try {
|
|
136
|
+
await this.db.runTransaction(async (transaction) => {
|
|
137
|
+
const practitionerDoc = await transaction.get(practitionerRef);
|
|
138
|
+
if (!practitionerDoc.exists) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Practitioner ${practitionerId} does not exist for procedure update`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const practitionerData = practitionerDoc.data();
|
|
145
|
+
if (!practitionerData) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Practitioner ${practitionerId} data is empty for procedure update`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Get current procedures info array
|
|
152
|
+
const proceduresInfo = practitionerData.proceduresInfo || [];
|
|
153
|
+
|
|
154
|
+
// Remove the old procedure summary
|
|
155
|
+
const updatedProceduresInfo = proceduresInfo.filter(
|
|
156
|
+
(p: ProcedureSummaryInfo) => p.id !== procedureId
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Add the updated procedure summary
|
|
160
|
+
updatedProceduresInfo.push(procedureSummary);
|
|
161
|
+
|
|
162
|
+
// Update the practitioner document
|
|
163
|
+
transaction.update(practitionerRef, {
|
|
164
|
+
proceduresInfo: updatedProceduresInfo,
|
|
165
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
console.log(
|
|
170
|
+
`[ProcedureAggregationService] Successfully updated procedure ${procedureId} info in practitioner ${practitionerId}.`
|
|
171
|
+
);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(
|
|
174
|
+
`[ProcedureAggregationService] Error updating procedure ${procedureId} info in practitioner ${practitionerId}:`,
|
|
175
|
+
error
|
|
176
|
+
);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Updates procedure information in a clinic document
|
|
183
|
+
* @param clinicId - ID of the clinic where the procedure is performed
|
|
184
|
+
* @param procedureSummary - Updated summary information about the procedure
|
|
185
|
+
* @returns {Promise<void>}
|
|
186
|
+
*/
|
|
187
|
+
async updateProcedureInfoInClinic(
|
|
188
|
+
clinicId: string,
|
|
189
|
+
procedureSummary: ProcedureSummaryInfo
|
|
190
|
+
): Promise<void> {
|
|
191
|
+
if (!clinicId || !procedureSummary) {
|
|
192
|
+
console.log(
|
|
193
|
+
"[ProcedureAggregationService] Missing clinicId or procedureSummary for updating procedure in clinic. Skipping."
|
|
194
|
+
);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const procedureId = procedureSummary.id;
|
|
199
|
+
console.log(
|
|
200
|
+
`[ProcedureAggregationService] Updating procedure ${procedureId} info in clinic ${clinicId}.`
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
|
|
204
|
+
|
|
205
|
+
// This requires a transaction to avoid race conditions
|
|
206
|
+
try {
|
|
207
|
+
await this.db.runTransaction(async (transaction) => {
|
|
208
|
+
const clinicDoc = await transaction.get(clinicRef);
|
|
209
|
+
if (!clinicDoc.exists) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Clinic ${clinicId} does not exist for procedure update`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const clinicData = clinicDoc.data();
|
|
216
|
+
if (!clinicData) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Clinic ${clinicId} data is empty for procedure update`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Get current procedures info array
|
|
223
|
+
const proceduresInfo = clinicData.proceduresInfo || [];
|
|
224
|
+
|
|
225
|
+
// Remove the old procedure summary
|
|
226
|
+
const updatedProceduresInfo = proceduresInfo.filter(
|
|
227
|
+
(p: ProcedureSummaryInfo) => p.id !== procedureId
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// Add the updated procedure summary
|
|
231
|
+
updatedProceduresInfo.push(procedureSummary);
|
|
232
|
+
|
|
233
|
+
// Update the clinic document
|
|
234
|
+
transaction.update(clinicRef, {
|
|
235
|
+
proceduresInfo: updatedProceduresInfo,
|
|
236
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
console.log(
|
|
241
|
+
`[ProcedureAggregationService] Successfully updated procedure ${procedureId} info in clinic ${clinicId}.`
|
|
242
|
+
);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
console.error(
|
|
245
|
+
`[ProcedureAggregationService] Error updating procedure ${procedureId} info in clinic ${clinicId}:`,
|
|
246
|
+
error
|
|
247
|
+
);
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Updates procedure information in calendar events
|
|
254
|
+
* @param procedureId - ID of the procedure
|
|
255
|
+
* @param procedureInfo - Updated procedure information
|
|
256
|
+
* @returns {Promise<void>}
|
|
257
|
+
*/
|
|
258
|
+
async updateProcedureInfoInCalendarEvents(
|
|
259
|
+
procedureId: string,
|
|
260
|
+
procedureInfo: any
|
|
261
|
+
): Promise<void> {
|
|
262
|
+
if (!procedureId || !procedureInfo) {
|
|
263
|
+
console.log(
|
|
264
|
+
"[ProcedureAggregationService] Missing procedureId or procedureInfo for calendar update. Skipping."
|
|
265
|
+
);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(
|
|
270
|
+
`[ProcedureAggregationService] Querying upcoming calendar events for procedure ${procedureId} to update procedure info.`
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const now = admin.firestore.Timestamp.now();
|
|
274
|
+
// Use a Collection Group query
|
|
275
|
+
const calendarEventsQuery = this.db
|
|
276
|
+
.collectionGroup(CALENDAR_SUBCOLLECTION_ID)
|
|
277
|
+
.where("procedureId", "==", procedureId)
|
|
278
|
+
.where("eventTime.start", ">", now);
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
const snapshot = await calendarEventsQuery.get();
|
|
282
|
+
if (snapshot.empty) {
|
|
283
|
+
console.log(
|
|
284
|
+
`[ProcedureAggregationService] No upcoming calendar events found for procedure ${procedureId}. No procedure info updates needed.`
|
|
285
|
+
);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const batch = this.db.batch();
|
|
290
|
+
snapshot.docs.forEach((doc) => {
|
|
291
|
+
console.log(
|
|
292
|
+
`[ProcedureAggregationService] Updating procedure info for calendar event ${doc.ref.path}`
|
|
293
|
+
);
|
|
294
|
+
batch.update(doc.ref, {
|
|
295
|
+
procedureInfo: procedureInfo,
|
|
296
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
await batch.commit();
|
|
301
|
+
console.log(
|
|
302
|
+
`[ProcedureAggregationService] Successfully updated procedure info in ${snapshot.size} upcoming calendar events for procedure ${procedureId}.`
|
|
303
|
+
);
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error(
|
|
306
|
+
`[ProcedureAggregationService] Error updating procedure info in calendar events for procedure ${procedureId}:`,
|
|
307
|
+
error
|
|
308
|
+
);
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Cancels all upcoming calendar events for a procedure
|
|
315
|
+
* @param procedureId - ID of the procedure
|
|
316
|
+
* @returns {Promise<void>}
|
|
317
|
+
*/
|
|
318
|
+
async cancelUpcomingCalendarEventsForProcedure(
|
|
319
|
+
procedureId: string
|
|
320
|
+
): Promise<void> {
|
|
321
|
+
if (!procedureId) {
|
|
322
|
+
console.log(
|
|
323
|
+
"[ProcedureAggregationService] Missing procedureId for canceling calendar events. Skipping."
|
|
324
|
+
);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
console.log(
|
|
329
|
+
`[ProcedureAggregationService] Querying upcoming calendar events for procedure ${procedureId} to cancel.`
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const now = admin.firestore.Timestamp.now();
|
|
333
|
+
// Use a Collection Group query
|
|
334
|
+
const calendarEventsQuery = this.db
|
|
335
|
+
.collectionGroup(CALENDAR_SUBCOLLECTION_ID)
|
|
336
|
+
.where("procedureId", "==", procedureId)
|
|
337
|
+
.where("eventTime.start", ">", now);
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
const snapshot = await calendarEventsQuery.get();
|
|
341
|
+
if (snapshot.empty) {
|
|
342
|
+
console.log(
|
|
343
|
+
`[ProcedureAggregationService] No upcoming calendar events found for procedure ${procedureId}. No events to cancel.`
|
|
344
|
+
);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const batch = this.db.batch();
|
|
349
|
+
snapshot.docs.forEach((doc) => {
|
|
350
|
+
console.log(
|
|
351
|
+
`[ProcedureAggregationService] Canceling calendar event ${doc.ref.path}`
|
|
352
|
+
);
|
|
353
|
+
batch.update(doc.ref, {
|
|
354
|
+
status: "CANCELED",
|
|
355
|
+
cancelReason: "Procedure deleted or inactivated",
|
|
356
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
await batch.commit();
|
|
361
|
+
console.log(
|
|
362
|
+
`[ProcedureAggregationService] Successfully canceled ${snapshot.size} upcoming calendar events for procedure ${procedureId}.`
|
|
363
|
+
);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error(
|
|
366
|
+
`[ProcedureAggregationService] Error canceling calendar events for procedure ${procedureId}:`,
|
|
367
|
+
error
|
|
368
|
+
);
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Removes procedure from a practitioner when a procedure is deleted or inactivated
|
|
375
|
+
* @param practitionerId - ID of the practitioner who performs the procedure
|
|
376
|
+
* @param procedureId - ID of the procedure
|
|
377
|
+
* @returns {Promise<void>}
|
|
378
|
+
*/
|
|
379
|
+
async removeProcedureFromPractitioner(
|
|
380
|
+
practitionerId: string,
|
|
381
|
+
procedureId: string
|
|
382
|
+
): Promise<void> {
|
|
383
|
+
if (!practitionerId || !procedureId) {
|
|
384
|
+
console.log(
|
|
385
|
+
"[ProcedureAggregationService] Missing practitionerId or procedureId for removing procedure from practitioner. Skipping."
|
|
386
|
+
);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
console.log(
|
|
391
|
+
`[ProcedureAggregationService] Removing procedure ${procedureId} from practitioner ${practitionerId}.`
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
const practitionerRef = this.db
|
|
395
|
+
.collection(PRACTITIONERS_COLLECTION)
|
|
396
|
+
.doc(practitionerId);
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
await this.db.runTransaction(async (transaction) => {
|
|
400
|
+
const practitionerDoc = await transaction.get(practitionerRef);
|
|
401
|
+
if (!practitionerDoc.exists) {
|
|
402
|
+
throw new Error(
|
|
403
|
+
`Practitioner ${practitionerId} does not exist for procedure removal`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const practitionerData = practitionerDoc.data();
|
|
408
|
+
if (!practitionerData) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`Practitioner ${practitionerId} data is empty for procedure removal`
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Get current procedures info array
|
|
415
|
+
const proceduresInfo = practitionerData.proceduresInfo || [];
|
|
416
|
+
|
|
417
|
+
// Remove the procedure summary
|
|
418
|
+
const updatedProceduresInfo = proceduresInfo.filter(
|
|
419
|
+
(p: ProcedureSummaryInfo) => p.id !== procedureId
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
// Update the practitioner document
|
|
423
|
+
transaction.update(practitionerRef, {
|
|
424
|
+
procedureIds: admin.firestore.FieldValue.arrayRemove(procedureId),
|
|
425
|
+
proceduresInfo: updatedProceduresInfo,
|
|
426
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
console.log(
|
|
431
|
+
`[ProcedureAggregationService] Successfully removed procedure ${procedureId} from practitioner ${practitionerId}.`
|
|
432
|
+
);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.error(
|
|
435
|
+
`[ProcedureAggregationService] Error removing procedure ${procedureId} from practitioner ${practitionerId}:`,
|
|
436
|
+
error
|
|
437
|
+
);
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Removes procedure from a clinic when a procedure is deleted or inactivated
|
|
444
|
+
* @param clinicId - ID of the clinic where the procedure is performed
|
|
445
|
+
* @param procedureId - ID of the procedure
|
|
446
|
+
* @returns {Promise<void>}
|
|
447
|
+
*/
|
|
448
|
+
async removeProcedureFromClinic(
|
|
449
|
+
clinicId: string,
|
|
450
|
+
procedureId: string
|
|
451
|
+
): Promise<void> {
|
|
452
|
+
if (!clinicId || !procedureId) {
|
|
453
|
+
console.log(
|
|
454
|
+
"[ProcedureAggregationService] Missing clinicId or procedureId for removing procedure from clinic. Skipping."
|
|
455
|
+
);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
console.log(
|
|
460
|
+
`[ProcedureAggregationService] Removing procedure ${procedureId} from clinic ${clinicId}.`
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
await this.db.runTransaction(async (transaction) => {
|
|
467
|
+
const clinicDoc = await transaction.get(clinicRef);
|
|
468
|
+
if (!clinicDoc.exists) {
|
|
469
|
+
throw new Error(
|
|
470
|
+
`Clinic ${clinicId} does not exist for procedure removal`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const clinicData = clinicDoc.data();
|
|
475
|
+
if (!clinicData) {
|
|
476
|
+
throw new Error(
|
|
477
|
+
`Clinic ${clinicId} data is empty for procedure removal`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Get current procedures info array
|
|
482
|
+
const proceduresInfo = clinicData.proceduresInfo || [];
|
|
483
|
+
|
|
484
|
+
// Remove the procedure summary
|
|
485
|
+
const updatedProceduresInfo = proceduresInfo.filter(
|
|
486
|
+
(p: ProcedureSummaryInfo) => p.id !== procedureId
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
// Update the clinic document
|
|
490
|
+
transaction.update(clinicRef, {
|
|
491
|
+
procedures: admin.firestore.FieldValue.arrayRemove(procedureId),
|
|
492
|
+
proceduresInfo: updatedProceduresInfo,
|
|
493
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
console.log(
|
|
498
|
+
`[ProcedureAggregationService] Successfully removed procedure ${procedureId} from clinic ${clinicId}.`
|
|
499
|
+
);
|
|
500
|
+
} catch (error) {
|
|
501
|
+
console.error(
|
|
502
|
+
`[ProcedureAggregationService] Error removing procedure ${procedureId} from clinic ${clinicId}:`,
|
|
503
|
+
error
|
|
504
|
+
);
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
package/src/admin/index.ts
CHANGED
|
@@ -5,8 +5,25 @@ import {
|
|
|
5
5
|
NotificationType,
|
|
6
6
|
} from "../types/notifications";
|
|
7
7
|
import { UserRole } from "../types";
|
|
8
|
+
// Import types needed by admin consumers (like Cloud Functions)
|
|
9
|
+
import { Clinic, ClinicLocation } from "../types/clinic";
|
|
10
|
+
import { ClinicInfo } from "../types/profile";
|
|
11
|
+
import { Practitioner, PractitionerToken } from "../types/practitioner";
|
|
12
|
+
import { DoctorInfo } from "../types/clinic";
|
|
13
|
+
import { Procedure, ProcedureSummaryInfo } from "../types/procedure";
|
|
14
|
+
import { PatientProfile } from "../types/patient";
|
|
8
15
|
|
|
9
|
-
//
|
|
16
|
+
// Explicitly import the services to re-export them by name
|
|
17
|
+
import { ClinicAggregationService } from "./aggregation/clinic/clinic.aggregation.service";
|
|
18
|
+
import { PractitionerAggregationService } from "./aggregation/practitioner/practitioner.aggregation.service";
|
|
19
|
+
import { ProcedureAggregationService } from "./aggregation/procedure/procedure.aggregation.service";
|
|
20
|
+
import { PatientAggregationService } from "./aggregation/patient/patient.aggregation.service";
|
|
21
|
+
|
|
22
|
+
// Import mailing services
|
|
23
|
+
import { BaseMailingService } from "./mailing/base.mailing.service";
|
|
24
|
+
import { PractitionerInviteMailingService } from "./mailing/practitionerInvite/practitionerInvite.mailing";
|
|
25
|
+
|
|
26
|
+
// Re-export types
|
|
10
27
|
export type {
|
|
11
28
|
Notification,
|
|
12
29
|
BaseNotification,
|
|
@@ -16,13 +33,52 @@ export type {
|
|
|
16
33
|
AppointmentNotification,
|
|
17
34
|
} from "../types/notifications";
|
|
18
35
|
|
|
36
|
+
// Re-export types needed by cloud functions
|
|
37
|
+
export type { Clinic, ClinicLocation } from "../types/clinic";
|
|
38
|
+
export type { ClinicInfo } from "../types/profile";
|
|
39
|
+
export type { Practitioner, PractitionerToken } from "../types/practitioner";
|
|
40
|
+
export type { DoctorInfo } from "../types/clinic";
|
|
41
|
+
export type { Procedure, ProcedureSummaryInfo } from "../types/procedure";
|
|
42
|
+
export type { PatientProfile as Patient } from "../types/patient";
|
|
43
|
+
|
|
44
|
+
// Re-export enums/consts
|
|
19
45
|
export {
|
|
20
46
|
NotificationType,
|
|
21
47
|
NotificationStatus,
|
|
22
48
|
NOTIFICATIONS_COLLECTION,
|
|
23
49
|
} from "../types/notifications";
|
|
24
|
-
|
|
25
50
|
export { UserRole } from "../types";
|
|
26
51
|
|
|
27
|
-
// Export admin
|
|
28
|
-
export {
|
|
52
|
+
// Export admin classes/services explicitly by name
|
|
53
|
+
export {
|
|
54
|
+
NotificationsAdmin,
|
|
55
|
+
ClinicAggregationService,
|
|
56
|
+
PractitionerAggregationService,
|
|
57
|
+
ProcedureAggregationService,
|
|
58
|
+
PatientAggregationService,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Export mailing services
|
|
62
|
+
export { BaseMailingService, PractitionerInviteMailingService };
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Main entry point for the Admin module.
|
|
66
|
+
* This module contains services and utilities intended for administrative tasks,
|
|
67
|
+
* background processing, and potentially direct use by admin interfaces or Cloud Functions.
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
// --- Aggregation Services --- //
|
|
71
|
+
// Placeholder: export * from "./aggregation/practitioner/practitioner.aggregation.service";
|
|
72
|
+
// Placeholder: export * from "./aggregation/procedure/procedure.aggregation.service";
|
|
73
|
+
// Placeholder: export * from "./aggregation/patient/patient.aggregation.service";
|
|
74
|
+
|
|
75
|
+
// --- Other Admin Services/Utilities (Add as needed) --- //
|
|
76
|
+
// Example: export * from './user-management/user-management.service';
|
|
77
|
+
// Example: export * from './reporting/reporting.service';
|
|
78
|
+
|
|
79
|
+
console.log("[Admin Module] Initialized and services exported.");
|
|
80
|
+
|
|
81
|
+
// Note: Ensure that services exported here are properly initialized
|
|
82
|
+
// if they have dependencies (like Firestore db) that need to be injected.
|
|
83
|
+
// The initialization pattern might differ depending on how this module is consumed
|
|
84
|
+
// (e.g., within a larger NestJS app vs. standalone Cloud Functions).
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Mailing Services
|
|
2
|
+
|
|
3
|
+
This module provides mailing services for sending automated emails from the application using Mailgun.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### 1. Install Dependencies
|
|
8
|
+
|
|
9
|
+
Make sure to install the required dependencies:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install mailgun-js firebase-admin firebase-functions
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. Configure Mailgun
|
|
16
|
+
|
|
17
|
+
To use the mailing services, you need to configure Mailgun credentials in your Firebase project:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
firebase functions:config:set mailgun.api_key="your-mailgun-api-key" mailgun.domain="your-mailgun-domain" mailgun.from="MedClinic <no-reply@your-domain.com>"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To enable test mode (emails won't actually be sent):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
firebase functions:config:set mailgun.test_mode="true"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 3. Deploy Cloud Functions
|
|
30
|
+
|
|
31
|
+
Deploy the cloud functions to start handling email sending events:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
firebase deploy --only functions
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Available Services
|
|
38
|
+
|
|
39
|
+
### Practitioner Invitation Service
|
|
40
|
+
|
|
41
|
+
Sends email invitations to practitioners when they are invited to join a clinic.
|
|
42
|
+
|
|
43
|
+
#### How it works:
|
|
44
|
+
|
|
45
|
+
1. When a new practitioner token is created in the Firestore database, the `onPractitionerTokenCreated` cloud function is triggered.
|
|
46
|
+
2. The function uses the `PractitionerInviteMailingService` to send an invitation email to the practitioner.
|
|
47
|
+
3. The email contains the token needed to claim their profile and instructions for registration.
|
|
48
|
+
|
|
49
|
+
#### Example Usage in Code:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { PractitionerInviteMailingService } from "./admin/mailing";
|
|
53
|
+
|
|
54
|
+
// Create a new instance of the service
|
|
55
|
+
const mailingService = new PractitionerInviteMailingService();
|
|
56
|
+
|
|
57
|
+
// Send an invitation email
|
|
58
|
+
await mailingService.sendInvitationEmail({
|
|
59
|
+
token: {
|
|
60
|
+
id: "token-id",
|
|
61
|
+
token: "ABC123",
|
|
62
|
+
practitionerId: "practitioner-id",
|
|
63
|
+
email: "doctor@example.com",
|
|
64
|
+
clinicId: "clinic-id",
|
|
65
|
+
expiresAt: admin.firestore.Timestamp.fromDate(new Date()),
|
|
66
|
+
},
|
|
67
|
+
practitioner: {
|
|
68
|
+
firstName: "John",
|
|
69
|
+
lastName: "Doe",
|
|
70
|
+
},
|
|
71
|
+
clinic: {
|
|
72
|
+
name: "Example Clinic",
|
|
73
|
+
contactEmail: "admin@example.com",
|
|
74
|
+
contactName: "Clinic Admin",
|
|
75
|
+
},
|
|
76
|
+
options: {
|
|
77
|
+
registrationUrl: "https://your-app.com/register",
|
|
78
|
+
customSubject: "Join Our Clinic",
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Adding New Mailing Services
|
|
84
|
+
|
|
85
|
+
To add a new mailing service:
|
|
86
|
+
|
|
87
|
+
1. Create a new directory in the `mailing` folder for your service
|
|
88
|
+
2. Create a service class that extends `BaseMailingService`
|
|
89
|
+
3. Create an HTML template in a `templates` subfolder
|
|
90
|
+
4. Create a cloud function to trigger the email sending
|
|
91
|
+
5. Export your new service in the index files
|
|
92
|
+
|
|
93
|
+
## Logging
|
|
94
|
+
|
|
95
|
+
All email sending attempts are logged to the `email_logs` collection in Firestore for tracking and debugging purposes.
|