@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
package/dist/admin/index.js
CHANGED
|
@@ -30,10 +30,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/admin/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
BaseMailingService: () => BaseMailingService,
|
|
34
|
+
ClinicAggregationService: () => ClinicAggregationService,
|
|
33
35
|
NOTIFICATIONS_COLLECTION: () => NOTIFICATIONS_COLLECTION,
|
|
34
36
|
NotificationStatus: () => NotificationStatus,
|
|
35
37
|
NotificationType: () => NotificationType,
|
|
36
38
|
NotificationsAdmin: () => NotificationsAdmin,
|
|
39
|
+
PatientAggregationService: () => PatientAggregationService,
|
|
40
|
+
PractitionerAggregationService: () => PractitionerAggregationService,
|
|
41
|
+
PractitionerInviteMailingService: () => PractitionerInviteMailingService,
|
|
42
|
+
ProcedureAggregationService: () => ProcedureAggregationService,
|
|
37
43
|
UserRole: () => UserRole
|
|
38
44
|
});
|
|
39
45
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -60,9 +66,9 @@ var NotificationStatus = /* @__PURE__ */ ((NotificationStatus2) => {
|
|
|
60
66
|
var admin = __toESM(require("firebase-admin"));
|
|
61
67
|
var import_expo_server_sdk = require("expo-server-sdk");
|
|
62
68
|
var NotificationsAdmin = class {
|
|
63
|
-
constructor(
|
|
69
|
+
constructor(firestore7) {
|
|
64
70
|
this.expo = new import_expo_server_sdk.Expo();
|
|
65
|
-
this.db =
|
|
71
|
+
this.db = firestore7 || admin.firestore();
|
|
66
72
|
}
|
|
67
73
|
/**
|
|
68
74
|
* Dohvata notifikaciju po ID-u
|
|
@@ -226,6 +232,1663 @@ var NotificationsAdmin = class {
|
|
|
226
232
|
}
|
|
227
233
|
};
|
|
228
234
|
|
|
235
|
+
// src/admin/aggregation/clinic/clinic.aggregation.service.ts
|
|
236
|
+
var admin2 = __toESM(require("firebase-admin"));
|
|
237
|
+
|
|
238
|
+
// src/types/practitioner/index.ts
|
|
239
|
+
var PRACTITIONERS_COLLECTION = "practitioners";
|
|
240
|
+
|
|
241
|
+
// src/types/procedure/index.ts
|
|
242
|
+
var PROCEDURES_COLLECTION = "procedures";
|
|
243
|
+
|
|
244
|
+
// src/types/clinic/index.ts
|
|
245
|
+
var CLINIC_GROUPS_COLLECTION = "clinic_groups";
|
|
246
|
+
var CLINICS_COLLECTION = "clinics";
|
|
247
|
+
|
|
248
|
+
// src/types/patient/index.ts
|
|
249
|
+
var PATIENTS_COLLECTION = "patients";
|
|
250
|
+
|
|
251
|
+
// src/admin/aggregation/clinic/clinic.aggregation.service.ts
|
|
252
|
+
var CALENDAR_SUBCOLLECTION_ID = "calendar";
|
|
253
|
+
var ClinicAggregationService = class {
|
|
254
|
+
/**
|
|
255
|
+
* Constructor for ClinicAggregationService.
|
|
256
|
+
* @param firestore Optional Firestore instance. If not provided, it uses the default admin SDK instance.
|
|
257
|
+
*/
|
|
258
|
+
constructor(firestore7) {
|
|
259
|
+
this.db = firestore7 || admin2.firestore();
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Adds clinic information to a clinic group when a new clinic is created
|
|
263
|
+
* @param clinicGroupId - ID of the parent clinic group
|
|
264
|
+
* @param clinicInfo - The clinic information to add to the group
|
|
265
|
+
* @returns {Promise<void>}
|
|
266
|
+
* @throws Will throw an error if the batch write fails
|
|
267
|
+
*/
|
|
268
|
+
async addClinicToClinicGroup(clinicGroupId, clinicInfo) {
|
|
269
|
+
if (!clinicGroupId) {
|
|
270
|
+
console.log(
|
|
271
|
+
"[ClinicAggregationService] No Clinic Group ID provided for clinic addition. Skipping."
|
|
272
|
+
);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const clinicId = clinicInfo.id;
|
|
276
|
+
const groupRef = this.db.collection(CLINIC_GROUPS_COLLECTION).doc(clinicGroupId);
|
|
277
|
+
console.log(
|
|
278
|
+
`[ClinicAggregationService] Adding ClinicInfo (ID: ${clinicId}) to Clinic Group ${clinicGroupId}.`
|
|
279
|
+
);
|
|
280
|
+
try {
|
|
281
|
+
await groupRef.update({
|
|
282
|
+
clinicsInfo: admin2.firestore.FieldValue.arrayUnion(clinicInfo),
|
|
283
|
+
clinicIds: admin2.firestore.FieldValue.arrayUnion(clinicId),
|
|
284
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
285
|
+
});
|
|
286
|
+
console.log(
|
|
287
|
+
`[ClinicAggregationService] Successfully added ClinicInfo (ID: ${clinicId}) to Clinic Group ${clinicGroupId}.`
|
|
288
|
+
);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error(
|
|
291
|
+
`[ClinicAggregationService] Error adding ClinicInfo (ID: ${clinicId}) to Clinic Group ${clinicGroupId}:`,
|
|
292
|
+
error
|
|
293
|
+
);
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Updates the ClinicInfo within the clinicsInfo array for multiple practitioners.
|
|
299
|
+
* This method is designed to be called when a clinic's core information changes.
|
|
300
|
+
* @param practitionerIds - IDs of practitioners associated with the clinic.
|
|
301
|
+
* @param clinicInfo - The updated ClinicInfo object to aggregate.
|
|
302
|
+
* @returns {Promise<void>}
|
|
303
|
+
* @throws Will throw an error if the batch write fails.
|
|
304
|
+
*/
|
|
305
|
+
async updateClinicInfoInPractitioners(practitionerIds, clinicInfo) {
|
|
306
|
+
if (!practitionerIds || practitionerIds.length === 0) {
|
|
307
|
+
console.log(
|
|
308
|
+
"[ClinicAggregationService] No practitioner IDs provided for clinic info update. Skipping."
|
|
309
|
+
);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const batch = this.db.batch();
|
|
313
|
+
const clinicId = clinicInfo.id;
|
|
314
|
+
console.log(
|
|
315
|
+
`[ClinicAggregationService] Starting batch update of ClinicInfo (ID: ${clinicId}) in ${practitionerIds.length} practitioners.`
|
|
316
|
+
);
|
|
317
|
+
for (const practitionerId of practitionerIds) {
|
|
318
|
+
const practitionerRef = this.db.collection(PRACTITIONERS_COLLECTION).doc(practitionerId);
|
|
319
|
+
batch.update(practitionerRef, {
|
|
320
|
+
clinicsInfo: admin2.firestore.FieldValue.arrayRemove({ id: clinicId }),
|
|
321
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
322
|
+
});
|
|
323
|
+
batch.update(practitionerRef, {
|
|
324
|
+
clinicsInfo: admin2.firestore.FieldValue.arrayUnion(clinicInfo),
|
|
325
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
await batch.commit();
|
|
330
|
+
console.log(
|
|
331
|
+
`[ClinicAggregationService] Successfully updated ClinicInfo (ID: ${clinicId}) in ${practitionerIds.length} practitioners.`
|
|
332
|
+
);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error(
|
|
335
|
+
`[ClinicAggregationService] Error committing batch update for ClinicInfo (ID: ${clinicId}) in practitioners:`,
|
|
336
|
+
error
|
|
337
|
+
);
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Updates the aggregated clinicInfo field within relevant Procedure documents.
|
|
343
|
+
* @param procedureIds IDs of procedures performed at the clinic.
|
|
344
|
+
* @param clinicInfo The updated ClinicInfo object.
|
|
345
|
+
*/
|
|
346
|
+
async updateClinicInfoInProcedures(procedureIds, clinicInfo) {
|
|
347
|
+
if (!procedureIds || procedureIds.length === 0) {
|
|
348
|
+
console.log(
|
|
349
|
+
"[ClinicAggregationService] No procedure IDs provided for clinic info update. Skipping."
|
|
350
|
+
);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
const batch = this.db.batch();
|
|
354
|
+
const clinicId = clinicInfo.id;
|
|
355
|
+
console.log(
|
|
356
|
+
`[ClinicAggregationService] Starting batch update of clinicInfo field (for Clinic ID: ${clinicId}) in ${procedureIds.length} procedures.`
|
|
357
|
+
);
|
|
358
|
+
for (const procedureId of procedureIds) {
|
|
359
|
+
const procedureRef = this.db.collection(PROCEDURES_COLLECTION).doc(procedureId);
|
|
360
|
+
batch.update(procedureRef, {
|
|
361
|
+
clinicInfo,
|
|
362
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
await batch.commit();
|
|
367
|
+
console.log(
|
|
368
|
+
`[ClinicAggregationService] Successfully updated clinicInfo field in ${procedureIds.length} procedures for clinic ${clinicId}.`
|
|
369
|
+
);
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error(
|
|
372
|
+
`[ClinicAggregationService] Error committing batch update for clinicInfo field in procedures (Clinic ID: ${clinicId}):`,
|
|
373
|
+
error
|
|
374
|
+
);
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Updates the aggregated clinicsInfo array within the parent ClinicGroup document.
|
|
380
|
+
* @param clinicGroupId The ID of the parent clinic group.
|
|
381
|
+
* @param clinicInfo The updated ClinicInfo object.
|
|
382
|
+
*/
|
|
383
|
+
async updateClinicInfoInClinicGroup(clinicGroupId, clinicInfo) {
|
|
384
|
+
if (!clinicGroupId) {
|
|
385
|
+
console.log(
|
|
386
|
+
"[ClinicAggregationService] No Clinic Group ID provided for clinic info update. Skipping."
|
|
387
|
+
);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const batch = this.db.batch();
|
|
391
|
+
const clinicId = clinicInfo.id;
|
|
392
|
+
const groupRef = this.db.collection(CLINIC_GROUPS_COLLECTION).doc(clinicGroupId);
|
|
393
|
+
console.log(
|
|
394
|
+
`[ClinicAggregationService] Starting update of ClinicInfo (ID: ${clinicId}) in Clinic Group ${clinicGroupId}.`
|
|
395
|
+
);
|
|
396
|
+
batch.update(groupRef, {
|
|
397
|
+
clinicsInfo: admin2.firestore.FieldValue.arrayRemove({ id: clinicId }),
|
|
398
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
399
|
+
});
|
|
400
|
+
batch.update(groupRef, {
|
|
401
|
+
clinicsInfo: admin2.firestore.FieldValue.arrayUnion(clinicInfo),
|
|
402
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
403
|
+
});
|
|
404
|
+
try {
|
|
405
|
+
await batch.commit();
|
|
406
|
+
console.log(
|
|
407
|
+
`[ClinicAggregationService] Successfully updated ClinicInfo (ID: ${clinicId}) in Clinic Group ${clinicGroupId}.`
|
|
408
|
+
);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error(
|
|
411
|
+
`[ClinicAggregationService] Error committing update for ClinicInfo (ID: ${clinicId}) in Clinic Group ${clinicGroupId}:`,
|
|
412
|
+
error
|
|
413
|
+
);
|
|
414
|
+
throw error;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Updates relevant clinic information within Patient documents.
|
|
419
|
+
* NOTE: PatientProfile stores an array of PatientClinic objects, which only contain
|
|
420
|
+
* clinicId, assignedAt, assignedBy, isActive, notes. It does *not* store aggregated
|
|
421
|
+
* ClinicInfo (like name, photo). Therefore, this method currently has limited use
|
|
422
|
+
* for general clinic info updates. It might be relevant if Clinic.isActive status changes.
|
|
423
|
+
*
|
|
424
|
+
* @param patientIds IDs of patients associated with the clinic (e.g., from PatientProfile.clinicIds).
|
|
425
|
+
* @param clinicInfo The updated ClinicInfo object (primarily for logging/context).
|
|
426
|
+
* @param clinicIsActive The current active status of the updated clinic.
|
|
427
|
+
*/
|
|
428
|
+
async updateClinicInfoInPatients(patientIds, clinicInfo, clinicIsActive) {
|
|
429
|
+
if (!patientIds || patientIds.length === 0) {
|
|
430
|
+
console.log(
|
|
431
|
+
"[ClinicAggregationService] No patient IDs provided for clinic info update. Skipping."
|
|
432
|
+
);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const clinicId = clinicInfo.id;
|
|
436
|
+
console.warn(
|
|
437
|
+
`[ClinicAggregationService] updateClinicInfoInPatients called for clinic ${clinicId}. Current PatientProfile structure doesn't store full ClinicInfo per patient clinic entry. Consider specific logic if Clinic active status changes or if structure evolves. No direct patient updates performed by this method for info changes. Clinic isActive: ${clinicIsActive}`,
|
|
438
|
+
{ patientIds, clinicInfo }
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Updates the eventLocation for upcoming calendar events associated with a specific clinic.
|
|
443
|
+
* Uses a collection group query to find relevant events across all potential parent collections.
|
|
444
|
+
*
|
|
445
|
+
* @param clinicId The ID of the clinic whose location might have changed.
|
|
446
|
+
* @param newLocation The new ClinicLocation object.
|
|
447
|
+
*/
|
|
448
|
+
async updateClinicLocationInCalendarEvents(clinicId, newLocation) {
|
|
449
|
+
if (!clinicId || !newLocation) {
|
|
450
|
+
console.log(
|
|
451
|
+
"[ClinicAggregationService] Missing clinicId or newLocation for calendar update. Skipping."
|
|
452
|
+
);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
console.log(
|
|
456
|
+
`[ClinicAggregationService] Querying upcoming calendar events via collection group '${CALENDAR_SUBCOLLECTION_ID}' for clinic ${clinicId} to update location.`
|
|
457
|
+
);
|
|
458
|
+
const now = admin2.firestore.Timestamp.now();
|
|
459
|
+
const calendarEventsQuery = this.db.collectionGroup(CALENDAR_SUBCOLLECTION_ID).where("clinicBranchId", "==", clinicId).where("eventTime.start", ">", now);
|
|
460
|
+
try {
|
|
461
|
+
const snapshot = await calendarEventsQuery.get();
|
|
462
|
+
if (snapshot.empty) {
|
|
463
|
+
console.log(
|
|
464
|
+
`[ClinicAggregationService] No upcoming calendar events found via collection group for clinic ${clinicId}. No location updates needed.`
|
|
465
|
+
);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const batch = this.db.batch();
|
|
469
|
+
snapshot.docs.forEach((doc) => {
|
|
470
|
+
console.log(
|
|
471
|
+
`[ClinicAggregationService] Updating location for calendar event ${doc.ref.path}`
|
|
472
|
+
);
|
|
473
|
+
batch.update(doc.ref, {
|
|
474
|
+
eventLocation: newLocation,
|
|
475
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
await batch.commit();
|
|
479
|
+
console.log(
|
|
480
|
+
`[ClinicAggregationService] Successfully updated location in ${snapshot.size} upcoming calendar events via collection group for clinic ${clinicId}.`
|
|
481
|
+
);
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.error(
|
|
484
|
+
`[ClinicAggregationService] Error updating location in calendar events via collection group for clinic ${clinicId}:`,
|
|
485
|
+
error
|
|
486
|
+
);
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Updates clinic info in all upcoming calendar events associated with a specific clinic.
|
|
492
|
+
* @param clinicId The ID of the clinic whose info has been updated.
|
|
493
|
+
* @param clinicInfo The updated ClinicInfo object.
|
|
494
|
+
*/
|
|
495
|
+
async updateClinicInfoInCalendarEvents(clinicId, clinicInfo) {
|
|
496
|
+
if (!clinicId || !clinicInfo) {
|
|
497
|
+
console.log(
|
|
498
|
+
"[ClinicAggregationService] Missing clinicId or clinicInfo for calendar update. Skipping."
|
|
499
|
+
);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
console.log(
|
|
503
|
+
`[ClinicAggregationService] Querying upcoming calendar events for clinic ${clinicId} to update clinic info.`
|
|
504
|
+
);
|
|
505
|
+
const now = admin2.firestore.Timestamp.now();
|
|
506
|
+
const calendarEventsQuery = this.db.collectionGroup(CALENDAR_SUBCOLLECTION_ID).where("clinicBranchId", "==", clinicId).where("eventTime.start", ">", now);
|
|
507
|
+
try {
|
|
508
|
+
const snapshot = await calendarEventsQuery.get();
|
|
509
|
+
if (snapshot.empty) {
|
|
510
|
+
console.log(
|
|
511
|
+
`[ClinicAggregationService] No upcoming calendar events found for clinic ${clinicId}. No clinic info updates needed.`
|
|
512
|
+
);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const batch = this.db.batch();
|
|
516
|
+
snapshot.docs.forEach((doc) => {
|
|
517
|
+
console.log(
|
|
518
|
+
`[ClinicAggregationService] Updating clinic info for calendar event ${doc.ref.path}`
|
|
519
|
+
);
|
|
520
|
+
batch.update(doc.ref, {
|
|
521
|
+
clinicInfo,
|
|
522
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
await batch.commit();
|
|
526
|
+
console.log(
|
|
527
|
+
`[ClinicAggregationService] Successfully updated clinic info in ${snapshot.size} upcoming calendar events for clinic ${clinicId}.`
|
|
528
|
+
);
|
|
529
|
+
} catch (error) {
|
|
530
|
+
console.error(
|
|
531
|
+
`[ClinicAggregationService] Error updating clinic info in calendar events for clinic ${clinicId}:`,
|
|
532
|
+
error
|
|
533
|
+
);
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Removes clinic from practitioners when a clinic is deleted.
|
|
539
|
+
* @param practitionerIds IDs of practitioners associated with the clinic.
|
|
540
|
+
* @param clinicId The ID of the deleted clinic.
|
|
541
|
+
*/
|
|
542
|
+
async removeClinicFromPractitioners(practitionerIds, clinicId) {
|
|
543
|
+
if (!practitionerIds || practitionerIds.length === 0) {
|
|
544
|
+
console.log(
|
|
545
|
+
"[ClinicAggregationService] No practitioner IDs provided for clinic removal. Skipping."
|
|
546
|
+
);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const batch = this.db.batch();
|
|
550
|
+
console.log(
|
|
551
|
+
`[ClinicAggregationService] Starting batch removal of Clinic (ID: ${clinicId}) from ${practitionerIds.length} practitioners.`
|
|
552
|
+
);
|
|
553
|
+
for (const practitionerId of practitionerIds) {
|
|
554
|
+
const practitionerRef = this.db.collection(PRACTITIONERS_COLLECTION).doc(practitionerId);
|
|
555
|
+
batch.update(practitionerRef, {
|
|
556
|
+
clinicIds: admin2.firestore.FieldValue.arrayRemove(clinicId),
|
|
557
|
+
// Remove all clinic info objects where id matches the clinic ID
|
|
558
|
+
clinicsInfo: admin2.firestore.FieldValue.arrayRemove({ id: clinicId }),
|
|
559
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
try {
|
|
563
|
+
await batch.commit();
|
|
564
|
+
console.log(
|
|
565
|
+
`[ClinicAggregationService] Successfully removed Clinic (ID: ${clinicId}) from ${practitionerIds.length} practitioners.`
|
|
566
|
+
);
|
|
567
|
+
} catch (error) {
|
|
568
|
+
console.error(
|
|
569
|
+
`[ClinicAggregationService] Error committing batch removal for Clinic (ID: ${clinicId}) from practitioners:`,
|
|
570
|
+
error
|
|
571
|
+
);
|
|
572
|
+
throw error;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Inactivates all procedures associated with a deleted clinic
|
|
577
|
+
* @param procedureIds IDs of procedures associated with the clinic
|
|
578
|
+
*/
|
|
579
|
+
async inactivateProceduresForClinic(procedureIds) {
|
|
580
|
+
if (!procedureIds || procedureIds.length === 0) {
|
|
581
|
+
console.log(
|
|
582
|
+
"[ClinicAggregationService] No procedure IDs provided for inactivation. Skipping."
|
|
583
|
+
);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const batch = this.db.batch();
|
|
587
|
+
console.log(
|
|
588
|
+
`[ClinicAggregationService] Starting inactivation of ${procedureIds.length} procedures.`
|
|
589
|
+
);
|
|
590
|
+
for (const procedureId of procedureIds) {
|
|
591
|
+
const procedureRef = this.db.collection(PROCEDURES_COLLECTION).doc(procedureId);
|
|
592
|
+
batch.update(procedureRef, {
|
|
593
|
+
isActive: false,
|
|
594
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
try {
|
|
598
|
+
await batch.commit();
|
|
599
|
+
console.log(
|
|
600
|
+
`[ClinicAggregationService] Successfully inactivated ${procedureIds.length} procedures.`
|
|
601
|
+
);
|
|
602
|
+
} catch (error) {
|
|
603
|
+
console.error(
|
|
604
|
+
`[ClinicAggregationService] Error committing batch inactivation of procedures:`,
|
|
605
|
+
error
|
|
606
|
+
);
|
|
607
|
+
throw error;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Removes clinic from clinic group when a clinic is deleted
|
|
612
|
+
* @param clinicGroupId ID of the parent clinic group
|
|
613
|
+
* @param clinicId ID of the deleted clinic
|
|
614
|
+
*/
|
|
615
|
+
async removeClinicFromClinicGroup(clinicGroupId, clinicId) {
|
|
616
|
+
if (!clinicGroupId) {
|
|
617
|
+
console.log(
|
|
618
|
+
"[ClinicAggregationService] No Clinic Group ID provided for clinic removal. Skipping."
|
|
619
|
+
);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
const groupRef = this.db.collection(CLINIC_GROUPS_COLLECTION).doc(clinicGroupId);
|
|
623
|
+
console.log(
|
|
624
|
+
`[ClinicAggregationService] Removing Clinic (ID: ${clinicId}) from Clinic Group ${clinicGroupId}.`
|
|
625
|
+
);
|
|
626
|
+
try {
|
|
627
|
+
await groupRef.update({
|
|
628
|
+
clinicIds: admin2.firestore.FieldValue.arrayRemove(clinicId),
|
|
629
|
+
// Remove all clinic info objects where id matches the clinic ID
|
|
630
|
+
clinicsInfo: admin2.firestore.FieldValue.arrayRemove({ id: clinicId }),
|
|
631
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
632
|
+
});
|
|
633
|
+
console.log(
|
|
634
|
+
`[ClinicAggregationService] Successfully removed Clinic (ID: ${clinicId}) from Clinic Group ${clinicGroupId}.`
|
|
635
|
+
);
|
|
636
|
+
} catch (error) {
|
|
637
|
+
console.error(
|
|
638
|
+
`[ClinicAggregationService] Error removing Clinic (ID: ${clinicId}) from Clinic Group ${clinicGroupId}:`,
|
|
639
|
+
error
|
|
640
|
+
);
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Removes clinic from patients when a clinic is deleted
|
|
646
|
+
* @param patientIds IDs of patients associated with the clinic
|
|
647
|
+
* @param clinicId ID of the deleted clinic
|
|
648
|
+
*/
|
|
649
|
+
async removeClinicFromPatients(patientIds, clinicId) {
|
|
650
|
+
if (!patientIds || patientIds.length === 0) {
|
|
651
|
+
console.log(
|
|
652
|
+
"[ClinicAggregationService] No patient IDs provided for clinic removal. Skipping."
|
|
653
|
+
);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
const batch = this.db.batch();
|
|
657
|
+
console.log(
|
|
658
|
+
`[ClinicAggregationService] Starting batch removal of Clinic (ID: ${clinicId}) from ${patientIds.length} patients.`
|
|
659
|
+
);
|
|
660
|
+
for (const patientId of patientIds) {
|
|
661
|
+
const patientRef = this.db.collection(PATIENTS_COLLECTION).doc(patientId);
|
|
662
|
+
batch.update(patientRef, {
|
|
663
|
+
clinicIds: admin2.firestore.FieldValue.arrayRemove(clinicId),
|
|
664
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
try {
|
|
668
|
+
await batch.commit();
|
|
669
|
+
console.log(
|
|
670
|
+
`[ClinicAggregationService] Successfully removed Clinic (ID: ${clinicId}) from ${patientIds.length} patients.`
|
|
671
|
+
);
|
|
672
|
+
} catch (error) {
|
|
673
|
+
console.error(
|
|
674
|
+
`[ClinicAggregationService] Error committing batch removal for Clinic (ID: ${clinicId}) from patients:`,
|
|
675
|
+
error
|
|
676
|
+
);
|
|
677
|
+
throw error;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Cancels all upcoming calendar events associated with a deleted clinic
|
|
682
|
+
* @param clinicId ID of the deleted clinic
|
|
683
|
+
*/
|
|
684
|
+
async cancelUpcomingCalendarEventsForClinic(clinicId) {
|
|
685
|
+
if (!clinicId) {
|
|
686
|
+
console.log(
|
|
687
|
+
"[ClinicAggregationService] No clinic ID provided for canceling calendar events. Skipping."
|
|
688
|
+
);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
console.log(
|
|
692
|
+
`[ClinicAggregationService] Querying upcoming calendar events for clinic ${clinicId} to cancel.`
|
|
693
|
+
);
|
|
694
|
+
const now = admin2.firestore.Timestamp.now();
|
|
695
|
+
const calendarEventsQuery = this.db.collectionGroup(CALENDAR_SUBCOLLECTION_ID).where("clinicBranchId", "==", clinicId).where("eventTime.start", ">", now);
|
|
696
|
+
try {
|
|
697
|
+
const snapshot = await calendarEventsQuery.get();
|
|
698
|
+
if (snapshot.empty) {
|
|
699
|
+
console.log(
|
|
700
|
+
`[ClinicAggregationService] No upcoming calendar events found for clinic ${clinicId}. No events to cancel.`
|
|
701
|
+
);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const batch = this.db.batch();
|
|
705
|
+
snapshot.docs.forEach((doc) => {
|
|
706
|
+
console.log(
|
|
707
|
+
`[ClinicAggregationService] Canceling calendar event ${doc.ref.path}`
|
|
708
|
+
);
|
|
709
|
+
batch.update(doc.ref, {
|
|
710
|
+
status: "CANCELED",
|
|
711
|
+
cancelReason: "Clinic deleted",
|
|
712
|
+
updatedAt: admin2.firestore.FieldValue.serverTimestamp()
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
await batch.commit();
|
|
716
|
+
console.log(
|
|
717
|
+
`[ClinicAggregationService] Successfully canceled ${snapshot.size} upcoming calendar events for clinic ${clinicId}.`
|
|
718
|
+
);
|
|
719
|
+
} catch (error) {
|
|
720
|
+
console.error(
|
|
721
|
+
`[ClinicAggregationService] Error canceling calendar events for clinic ${clinicId}:`,
|
|
722
|
+
error
|
|
723
|
+
);
|
|
724
|
+
throw error;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
// src/admin/aggregation/practitioner/practitioner.aggregation.service.ts
|
|
730
|
+
var admin3 = __toESM(require("firebase-admin"));
|
|
731
|
+
var CALENDAR_SUBCOLLECTION_ID2 = "calendar";
|
|
732
|
+
var PractitionerAggregationService = class {
|
|
733
|
+
constructor(firestore7) {
|
|
734
|
+
this.db = firestore7 || admin3.firestore();
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Adds practitioner information to a clinic when a new practitioner is created
|
|
738
|
+
* @param clinicId - ID of the clinic to update
|
|
739
|
+
* @param doctorInfo - Doctor information to add to the clinic
|
|
740
|
+
* @returns {Promise<void>}
|
|
741
|
+
*/
|
|
742
|
+
async addPractitionerToClinic(clinicId, doctorInfo) {
|
|
743
|
+
if (!clinicId || !doctorInfo) {
|
|
744
|
+
console.log(
|
|
745
|
+
"[PractitionerAggregationService] Missing clinicId or doctorInfo for adding practitioner to clinic. Skipping."
|
|
746
|
+
);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const practitionerId = doctorInfo.id;
|
|
750
|
+
console.log(
|
|
751
|
+
`[PractitionerAggregationService] Adding practitioner ${practitionerId} to clinic ${clinicId}.`
|
|
752
|
+
);
|
|
753
|
+
const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
|
|
754
|
+
try {
|
|
755
|
+
await clinicRef.update({
|
|
756
|
+
doctors: admin3.firestore.FieldValue.arrayUnion(practitionerId),
|
|
757
|
+
doctorsInfo: admin3.firestore.FieldValue.arrayUnion(doctorInfo),
|
|
758
|
+
updatedAt: admin3.firestore.FieldValue.serverTimestamp()
|
|
759
|
+
});
|
|
760
|
+
console.log(
|
|
761
|
+
`[PractitionerAggregationService] Successfully added practitioner ${practitionerId} to clinic ${clinicId}.`
|
|
762
|
+
);
|
|
763
|
+
} catch (error) {
|
|
764
|
+
console.error(
|
|
765
|
+
`[PractitionerAggregationService] Error adding practitioner ${practitionerId} to clinic ${clinicId}:`,
|
|
766
|
+
error
|
|
767
|
+
);
|
|
768
|
+
throw error;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Updates practitioner information in associated clinics
|
|
773
|
+
* @param clinicIds - IDs of clinics associated with the practitioner
|
|
774
|
+
* @param doctorInfo - Updated doctor information
|
|
775
|
+
* @returns {Promise<void>}
|
|
776
|
+
*/
|
|
777
|
+
async updatePractitionerInfoInClinics(clinicIds, doctorInfo) {
|
|
778
|
+
if (!clinicIds || clinicIds.length === 0 || !doctorInfo) {
|
|
779
|
+
console.log(
|
|
780
|
+
"[PractitionerAggregationService] Missing clinicIds or doctorInfo for updating practitioner in clinics. Skipping."
|
|
781
|
+
);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
const batch = this.db.batch();
|
|
785
|
+
const practitionerId = doctorInfo.id;
|
|
786
|
+
console.log(
|
|
787
|
+
`[PractitionerAggregationService] Starting batch update of practitioner ${practitionerId} in ${clinicIds.length} clinics.`
|
|
788
|
+
);
|
|
789
|
+
for (const clinicId of clinicIds) {
|
|
790
|
+
const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
|
|
791
|
+
batch.update(clinicRef, {
|
|
792
|
+
doctorsInfo: admin3.firestore.FieldValue.arrayRemove({
|
|
793
|
+
id: practitionerId
|
|
794
|
+
}),
|
|
795
|
+
updatedAt: admin3.firestore.FieldValue.serverTimestamp()
|
|
796
|
+
});
|
|
797
|
+
batch.update(clinicRef, {
|
|
798
|
+
doctorsInfo: admin3.firestore.FieldValue.arrayUnion(doctorInfo),
|
|
799
|
+
updatedAt: admin3.firestore.FieldValue.serverTimestamp()
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
await batch.commit();
|
|
804
|
+
console.log(
|
|
805
|
+
`[PractitionerAggregationService] Successfully updated practitioner ${practitionerId} info in ${clinicIds.length} clinics.`
|
|
806
|
+
);
|
|
807
|
+
} catch (error) {
|
|
808
|
+
console.error(
|
|
809
|
+
`[PractitionerAggregationService] Error updating practitioner ${practitionerId} info in clinics:`,
|
|
810
|
+
error
|
|
811
|
+
);
|
|
812
|
+
throw error;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Updates practitioner information in associated procedures
|
|
817
|
+
* @param procedureIds - IDs of procedures associated with the practitioner
|
|
818
|
+
* @param doctorInfo - Updated doctor information
|
|
819
|
+
* @returns {Promise<void>}
|
|
820
|
+
*/
|
|
821
|
+
async updatePractitionerInfoInProcedures(procedureIds, doctorInfo) {
|
|
822
|
+
if (!procedureIds || procedureIds.length === 0 || !doctorInfo) {
|
|
823
|
+
console.log(
|
|
824
|
+
"[PractitionerAggregationService] Missing procedureIds or doctorInfo for updating practitioner in procedures. Skipping."
|
|
825
|
+
);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
const batch = this.db.batch();
|
|
829
|
+
const practitionerId = doctorInfo.id;
|
|
830
|
+
console.log(
|
|
831
|
+
`[PractitionerAggregationService] Starting batch update of practitioner ${practitionerId} in ${procedureIds.length} procedures.`
|
|
832
|
+
);
|
|
833
|
+
for (const procedureId of procedureIds) {
|
|
834
|
+
const procedureRef = this.db.collection(PROCEDURES_COLLECTION).doc(procedureId);
|
|
835
|
+
batch.update(procedureRef, {
|
|
836
|
+
doctorInfo,
|
|
837
|
+
updatedAt: admin3.firestore.FieldValue.serverTimestamp()
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
try {
|
|
841
|
+
await batch.commit();
|
|
842
|
+
console.log(
|
|
843
|
+
`[PractitionerAggregationService] Successfully updated practitioner ${practitionerId} info in ${procedureIds.length} procedures.`
|
|
844
|
+
);
|
|
845
|
+
} catch (error) {
|
|
846
|
+
console.error(
|
|
847
|
+
`[PractitionerAggregationService] Error updating practitioner ${practitionerId} info in procedures:`,
|
|
848
|
+
error
|
|
849
|
+
);
|
|
850
|
+
throw error;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Updates practitioner information in calendar events
|
|
855
|
+
* @param practitionerId - ID of the practitioner
|
|
856
|
+
* @param practitionerInfo - Updated practitioner information
|
|
857
|
+
* @returns {Promise<void>}
|
|
858
|
+
*/
|
|
859
|
+
async updatePractitionerInfoInCalendarEvents(practitionerId, practitionerInfo) {
|
|
860
|
+
if (!practitionerId || !practitionerInfo) {
|
|
861
|
+
console.log(
|
|
862
|
+
"[PractitionerAggregationService] Missing practitionerId or practitionerInfo for calendar update. Skipping."
|
|
863
|
+
);
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
console.log(
|
|
867
|
+
`[PractitionerAggregationService] Querying upcoming calendar events for practitioner ${practitionerId} to update practitioner info.`
|
|
868
|
+
);
|
|
869
|
+
const now = admin3.firestore.Timestamp.now();
|
|
870
|
+
const calendarEventsQuery = this.db.collectionGroup(CALENDAR_SUBCOLLECTION_ID2).where("practitionerId", "==", practitionerId).where("eventTime.start", ">", now);
|
|
871
|
+
try {
|
|
872
|
+
const snapshot = await calendarEventsQuery.get();
|
|
873
|
+
if (snapshot.empty) {
|
|
874
|
+
console.log(
|
|
875
|
+
`[PractitionerAggregationService] No upcoming calendar events found for practitioner ${practitionerId}. No doctor info updates needed.`
|
|
876
|
+
);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
const batch = this.db.batch();
|
|
880
|
+
snapshot.docs.forEach((doc) => {
|
|
881
|
+
console.log(
|
|
882
|
+
`[PractitionerAggregationService] Updating practitioner info for calendar event ${doc.ref.path}`
|
|
883
|
+
);
|
|
884
|
+
batch.update(doc.ref, {
|
|
885
|
+
practitionerInfo,
|
|
886
|
+
updatedAt: admin3.firestore.FieldValue.serverTimestamp()
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
await batch.commit();
|
|
890
|
+
console.log(
|
|
891
|
+
`[PractitionerAggregationService] Successfully updated practitioner info in ${snapshot.size} upcoming calendar events for practitioner ${practitionerId}.`
|
|
892
|
+
);
|
|
893
|
+
} catch (error) {
|
|
894
|
+
console.error(
|
|
895
|
+
`[PractitionerAggregationService] Error updating practitioner info in calendar events for practitioner ${practitionerId}:`,
|
|
896
|
+
error
|
|
897
|
+
);
|
|
898
|
+
throw error;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Removes practitioner from clinics when a practitioner is deleted
|
|
903
|
+
* @param clinicIds - IDs of clinics associated with the practitioner
|
|
904
|
+
* @param practitionerId - ID of the deleted practitioner
|
|
905
|
+
* @returns {Promise<void>}
|
|
906
|
+
*/
|
|
907
|
+
async removePractitionerFromClinics(clinicIds, practitionerId) {
|
|
908
|
+
if (!clinicIds || clinicIds.length === 0 || !practitionerId) {
|
|
909
|
+
console.log(
|
|
910
|
+
"[PractitionerAggregationService] Missing clinicIds or practitionerId for removing practitioner from clinics. Skipping."
|
|
911
|
+
);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const batch = this.db.batch();
|
|
915
|
+
console.log(
|
|
916
|
+
`[PractitionerAggregationService] Starting batch removal of practitioner ${practitionerId} from ${clinicIds.length} clinics.`
|
|
917
|
+
);
|
|
918
|
+
for (const clinicId of clinicIds) {
|
|
919
|
+
const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
|
|
920
|
+
batch.update(clinicRef, {
|
|
921
|
+
doctors: admin3.firestore.FieldValue.arrayRemove(practitionerId),
|
|
922
|
+
// Remove all doctor info objects where id matches the practitioner ID
|
|
923
|
+
doctorsInfo: admin3.firestore.FieldValue.arrayRemove({
|
|
924
|
+
id: practitionerId
|
|
925
|
+
}),
|
|
926
|
+
updatedAt: admin3.firestore.FieldValue.serverTimestamp()
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
try {
|
|
930
|
+
await batch.commit();
|
|
931
|
+
console.log(
|
|
932
|
+
`[PractitionerAggregationService] Successfully removed practitioner ${practitionerId} from ${clinicIds.length} clinics.`
|
|
933
|
+
);
|
|
934
|
+
} catch (error) {
|
|
935
|
+
console.error(
|
|
936
|
+
`[PractitionerAggregationService] Error removing practitioner ${practitionerId} from clinics:`,
|
|
937
|
+
error
|
|
938
|
+
);
|
|
939
|
+
throw error;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Cancels all upcoming calendar events for a deleted practitioner
|
|
944
|
+
* @param practitionerId - ID of the deleted practitioner
|
|
945
|
+
* @returns {Promise<void>}
|
|
946
|
+
*/
|
|
947
|
+
async cancelUpcomingCalendarEventsForPractitioner(practitionerId) {
|
|
948
|
+
if (!practitionerId) {
|
|
949
|
+
console.log(
|
|
950
|
+
"[PractitionerAggregationService] Missing practitionerId for canceling calendar events. Skipping."
|
|
951
|
+
);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
console.log(
|
|
955
|
+
`[PractitionerAggregationService] Querying upcoming calendar events for practitioner ${practitionerId} to cancel.`
|
|
956
|
+
);
|
|
957
|
+
const now = admin3.firestore.Timestamp.now();
|
|
958
|
+
const calendarEventsQuery = this.db.collectionGroup(CALENDAR_SUBCOLLECTION_ID2).where("practitionerId", "==", practitionerId).where("eventTime.start", ">", now);
|
|
959
|
+
try {
|
|
960
|
+
const snapshot = await calendarEventsQuery.get();
|
|
961
|
+
if (snapshot.empty) {
|
|
962
|
+
console.log(
|
|
963
|
+
`[PractitionerAggregationService] No upcoming calendar events found for practitioner ${practitionerId}. No events to cancel.`
|
|
964
|
+
);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const batch = this.db.batch();
|
|
968
|
+
snapshot.docs.forEach((doc) => {
|
|
969
|
+
console.log(
|
|
970
|
+
`[PractitionerAggregationService] Canceling calendar event ${doc.ref.path}`
|
|
971
|
+
);
|
|
972
|
+
batch.update(doc.ref, {
|
|
973
|
+
status: "CANCELED",
|
|
974
|
+
cancelReason: "Practitioner deleted",
|
|
975
|
+
updatedAt: admin3.firestore.FieldValue.serverTimestamp()
|
|
976
|
+
});
|
|
977
|
+
});
|
|
978
|
+
await batch.commit();
|
|
979
|
+
console.log(
|
|
980
|
+
`[PractitionerAggregationService] Successfully canceled ${snapshot.size} upcoming calendar events for practitioner ${practitionerId}.`
|
|
981
|
+
);
|
|
982
|
+
} catch (error) {
|
|
983
|
+
console.error(
|
|
984
|
+
`[PractitionerAggregationService] Error canceling calendar events for practitioner ${practitionerId}:`,
|
|
985
|
+
error
|
|
986
|
+
);
|
|
987
|
+
throw error;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Removes practitioner from patients when a practitioner is deleted
|
|
992
|
+
* @param patientIds - IDs of patients associated with the practitioner
|
|
993
|
+
* @param practitionerId - ID of the deleted practitioner
|
|
994
|
+
* @returns {Promise<void>}
|
|
995
|
+
*/
|
|
996
|
+
async removePractitionerFromPatients(patientIds, practitionerId) {
|
|
997
|
+
if (!patientIds || patientIds.length === 0 || !practitionerId) {
|
|
998
|
+
console.log(
|
|
999
|
+
"[PractitionerAggregationService] Missing patientIds or practitionerId for removing practitioner from patients. Skipping."
|
|
1000
|
+
);
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
const batch = this.db.batch();
|
|
1004
|
+
console.log(
|
|
1005
|
+
`[PractitionerAggregationService] Starting batch removal of practitioner ${practitionerId} from ${patientIds.length} patients.`
|
|
1006
|
+
);
|
|
1007
|
+
for (const patientId of patientIds) {
|
|
1008
|
+
const patientRef = this.db.collection(PATIENTS_COLLECTION).doc(patientId);
|
|
1009
|
+
batch.update(patientRef, {
|
|
1010
|
+
doctorIds: admin3.firestore.FieldValue.arrayRemove(practitionerId),
|
|
1011
|
+
updatedAt: admin3.firestore.FieldValue.serverTimestamp()
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
try {
|
|
1015
|
+
await batch.commit();
|
|
1016
|
+
console.log(
|
|
1017
|
+
`[PractitionerAggregationService] Successfully removed practitioner ${practitionerId} from ${patientIds.length} patients.`
|
|
1018
|
+
);
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
console.error(
|
|
1021
|
+
`[PractitionerAggregationService] Error removing practitioner ${practitionerId} from patients:`,
|
|
1022
|
+
error
|
|
1023
|
+
);
|
|
1024
|
+
throw error;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Inactivates all procedures associated with a deleted practitioner
|
|
1029
|
+
* @param procedureIds - IDs of procedures provided by the practitioner
|
|
1030
|
+
* @returns {Promise<void>}
|
|
1031
|
+
*/
|
|
1032
|
+
async inactivateProceduresForPractitioner(procedureIds) {
|
|
1033
|
+
if (!procedureIds || procedureIds.length === 0) {
|
|
1034
|
+
console.log(
|
|
1035
|
+
"[PractitionerAggregationService] No procedure IDs provided for inactivation. Skipping."
|
|
1036
|
+
);
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
const batch = this.db.batch();
|
|
1040
|
+
console.log(
|
|
1041
|
+
`[PractitionerAggregationService] Starting inactivation of ${procedureIds.length} procedures.`
|
|
1042
|
+
);
|
|
1043
|
+
for (const procedureId of procedureIds) {
|
|
1044
|
+
const procedureRef = this.db.collection(PROCEDURES_COLLECTION).doc(procedureId);
|
|
1045
|
+
batch.update(procedureRef, {
|
|
1046
|
+
isActive: false,
|
|
1047
|
+
updatedAt: admin3.firestore.FieldValue.serverTimestamp()
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
await batch.commit();
|
|
1052
|
+
console.log(
|
|
1053
|
+
`[PractitionerAggregationService] Successfully inactivated ${procedureIds.length} procedures.`
|
|
1054
|
+
);
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
console.error(
|
|
1057
|
+
`[PractitionerAggregationService] Error committing batch inactivation of procedures:`,
|
|
1058
|
+
error
|
|
1059
|
+
);
|
|
1060
|
+
throw error;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
// src/admin/aggregation/procedure/procedure.aggregation.service.ts
|
|
1066
|
+
var admin4 = __toESM(require("firebase-admin"));
|
|
1067
|
+
var CALENDAR_SUBCOLLECTION_ID3 = "calendar";
|
|
1068
|
+
var ProcedureAggregationService = class {
|
|
1069
|
+
constructor(firestore7) {
|
|
1070
|
+
this.db = firestore7 || admin4.firestore();
|
|
1071
|
+
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Adds procedure information to a practitioner when a new procedure is created
|
|
1074
|
+
* @param practitionerId - ID of the practitioner who performs the procedure
|
|
1075
|
+
* @param procedureSummary - Summary information about the procedure
|
|
1076
|
+
* @returns {Promise<void>}
|
|
1077
|
+
*/
|
|
1078
|
+
async addProcedureToPractitioner(practitionerId, procedureSummary) {
|
|
1079
|
+
if (!practitionerId || !procedureSummary) {
|
|
1080
|
+
console.log(
|
|
1081
|
+
"[ProcedureAggregationService] Missing practitionerId or procedureSummary for adding procedure to practitioner. Skipping."
|
|
1082
|
+
);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
const procedureId = procedureSummary.id;
|
|
1086
|
+
console.log(
|
|
1087
|
+
`[ProcedureAggregationService] Adding procedure ${procedureId} to practitioner ${practitionerId}.`
|
|
1088
|
+
);
|
|
1089
|
+
const practitionerRef = this.db.collection(PRACTITIONERS_COLLECTION).doc(practitionerId);
|
|
1090
|
+
try {
|
|
1091
|
+
await practitionerRef.update({
|
|
1092
|
+
procedureIds: admin4.firestore.FieldValue.arrayUnion(procedureId),
|
|
1093
|
+
proceduresInfo: admin4.firestore.FieldValue.arrayUnion(procedureSummary),
|
|
1094
|
+
updatedAt: admin4.firestore.FieldValue.serverTimestamp()
|
|
1095
|
+
});
|
|
1096
|
+
console.log(
|
|
1097
|
+
`[ProcedureAggregationService] Successfully added procedure ${procedureId} to practitioner ${practitionerId}.`
|
|
1098
|
+
);
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
console.error(
|
|
1101
|
+
`[ProcedureAggregationService] Error adding procedure ${procedureId} to practitioner ${practitionerId}:`,
|
|
1102
|
+
error
|
|
1103
|
+
);
|
|
1104
|
+
throw error;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Adds procedure information to a clinic when a new procedure is created
|
|
1109
|
+
* @param clinicId - ID of the clinic where the procedure is performed
|
|
1110
|
+
* @param procedureSummary - Summary information about the procedure
|
|
1111
|
+
* @returns {Promise<void>}
|
|
1112
|
+
*/
|
|
1113
|
+
async addProcedureToClinic(clinicId, procedureSummary) {
|
|
1114
|
+
if (!clinicId || !procedureSummary) {
|
|
1115
|
+
console.log(
|
|
1116
|
+
"[ProcedureAggregationService] Missing clinicId or procedureSummary for adding procedure to clinic. Skipping."
|
|
1117
|
+
);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
const procedureId = procedureSummary.id;
|
|
1121
|
+
console.log(
|
|
1122
|
+
`[ProcedureAggregationService] Adding procedure ${procedureId} to clinic ${clinicId}.`
|
|
1123
|
+
);
|
|
1124
|
+
const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
|
|
1125
|
+
try {
|
|
1126
|
+
await clinicRef.update({
|
|
1127
|
+
procedures: admin4.firestore.FieldValue.arrayUnion(procedureId),
|
|
1128
|
+
proceduresInfo: admin4.firestore.FieldValue.arrayUnion(procedureSummary),
|
|
1129
|
+
updatedAt: admin4.firestore.FieldValue.serverTimestamp()
|
|
1130
|
+
});
|
|
1131
|
+
console.log(
|
|
1132
|
+
`[ProcedureAggregationService] Successfully added procedure ${procedureId} to clinic ${clinicId}.`
|
|
1133
|
+
);
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
console.error(
|
|
1136
|
+
`[ProcedureAggregationService] Error adding procedure ${procedureId} to clinic ${clinicId}:`,
|
|
1137
|
+
error
|
|
1138
|
+
);
|
|
1139
|
+
throw error;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Updates procedure information in a practitioner document
|
|
1144
|
+
* @param practitionerId - ID of the practitioner who performs the procedure
|
|
1145
|
+
* @param procedureSummary - Updated summary information about the procedure
|
|
1146
|
+
* @returns {Promise<void>}
|
|
1147
|
+
*/
|
|
1148
|
+
async updateProcedureInfoInPractitioner(practitionerId, procedureSummary) {
|
|
1149
|
+
if (!practitionerId || !procedureSummary) {
|
|
1150
|
+
console.log(
|
|
1151
|
+
"[ProcedureAggregationService] Missing practitionerId or procedureSummary for updating procedure in practitioner. Skipping."
|
|
1152
|
+
);
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const procedureId = procedureSummary.id;
|
|
1156
|
+
console.log(
|
|
1157
|
+
`[ProcedureAggregationService] Updating procedure ${procedureId} info in practitioner ${practitionerId}.`
|
|
1158
|
+
);
|
|
1159
|
+
const practitionerRef = this.db.collection(PRACTITIONERS_COLLECTION).doc(practitionerId);
|
|
1160
|
+
try {
|
|
1161
|
+
await this.db.runTransaction(async (transaction) => {
|
|
1162
|
+
const practitionerDoc = await transaction.get(practitionerRef);
|
|
1163
|
+
if (!practitionerDoc.exists) {
|
|
1164
|
+
throw new Error(
|
|
1165
|
+
`Practitioner ${practitionerId} does not exist for procedure update`
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
const practitionerData = practitionerDoc.data();
|
|
1169
|
+
if (!practitionerData) {
|
|
1170
|
+
throw new Error(
|
|
1171
|
+
`Practitioner ${practitionerId} data is empty for procedure update`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
const proceduresInfo = practitionerData.proceduresInfo || [];
|
|
1175
|
+
const updatedProceduresInfo = proceduresInfo.filter(
|
|
1176
|
+
(p) => p.id !== procedureId
|
|
1177
|
+
);
|
|
1178
|
+
updatedProceduresInfo.push(procedureSummary);
|
|
1179
|
+
transaction.update(practitionerRef, {
|
|
1180
|
+
proceduresInfo: updatedProceduresInfo,
|
|
1181
|
+
updatedAt: admin4.firestore.FieldValue.serverTimestamp()
|
|
1182
|
+
});
|
|
1183
|
+
});
|
|
1184
|
+
console.log(
|
|
1185
|
+
`[ProcedureAggregationService] Successfully updated procedure ${procedureId} info in practitioner ${practitionerId}.`
|
|
1186
|
+
);
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
console.error(
|
|
1189
|
+
`[ProcedureAggregationService] Error updating procedure ${procedureId} info in practitioner ${practitionerId}:`,
|
|
1190
|
+
error
|
|
1191
|
+
);
|
|
1192
|
+
throw error;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Updates procedure information in a clinic document
|
|
1197
|
+
* @param clinicId - ID of the clinic where the procedure is performed
|
|
1198
|
+
* @param procedureSummary - Updated summary information about the procedure
|
|
1199
|
+
* @returns {Promise<void>}
|
|
1200
|
+
*/
|
|
1201
|
+
async updateProcedureInfoInClinic(clinicId, procedureSummary) {
|
|
1202
|
+
if (!clinicId || !procedureSummary) {
|
|
1203
|
+
console.log(
|
|
1204
|
+
"[ProcedureAggregationService] Missing clinicId or procedureSummary for updating procedure in clinic. Skipping."
|
|
1205
|
+
);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
const procedureId = procedureSummary.id;
|
|
1209
|
+
console.log(
|
|
1210
|
+
`[ProcedureAggregationService] Updating procedure ${procedureId} info in clinic ${clinicId}.`
|
|
1211
|
+
);
|
|
1212
|
+
const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
|
|
1213
|
+
try {
|
|
1214
|
+
await this.db.runTransaction(async (transaction) => {
|
|
1215
|
+
const clinicDoc = await transaction.get(clinicRef);
|
|
1216
|
+
if (!clinicDoc.exists) {
|
|
1217
|
+
throw new Error(
|
|
1218
|
+
`Clinic ${clinicId} does not exist for procedure update`
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
const clinicData = clinicDoc.data();
|
|
1222
|
+
if (!clinicData) {
|
|
1223
|
+
throw new Error(
|
|
1224
|
+
`Clinic ${clinicId} data is empty for procedure update`
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
const proceduresInfo = clinicData.proceduresInfo || [];
|
|
1228
|
+
const updatedProceduresInfo = proceduresInfo.filter(
|
|
1229
|
+
(p) => p.id !== procedureId
|
|
1230
|
+
);
|
|
1231
|
+
updatedProceduresInfo.push(procedureSummary);
|
|
1232
|
+
transaction.update(clinicRef, {
|
|
1233
|
+
proceduresInfo: updatedProceduresInfo,
|
|
1234
|
+
updatedAt: admin4.firestore.FieldValue.serverTimestamp()
|
|
1235
|
+
});
|
|
1236
|
+
});
|
|
1237
|
+
console.log(
|
|
1238
|
+
`[ProcedureAggregationService] Successfully updated procedure ${procedureId} info in clinic ${clinicId}.`
|
|
1239
|
+
);
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
console.error(
|
|
1242
|
+
`[ProcedureAggregationService] Error updating procedure ${procedureId} info in clinic ${clinicId}:`,
|
|
1243
|
+
error
|
|
1244
|
+
);
|
|
1245
|
+
throw error;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Updates procedure information in calendar events
|
|
1250
|
+
* @param procedureId - ID of the procedure
|
|
1251
|
+
* @param procedureInfo - Updated procedure information
|
|
1252
|
+
* @returns {Promise<void>}
|
|
1253
|
+
*/
|
|
1254
|
+
async updateProcedureInfoInCalendarEvents(procedureId, procedureInfo) {
|
|
1255
|
+
if (!procedureId || !procedureInfo) {
|
|
1256
|
+
console.log(
|
|
1257
|
+
"[ProcedureAggregationService] Missing procedureId or procedureInfo for calendar update. Skipping."
|
|
1258
|
+
);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
console.log(
|
|
1262
|
+
`[ProcedureAggregationService] Querying upcoming calendar events for procedure ${procedureId} to update procedure info.`
|
|
1263
|
+
);
|
|
1264
|
+
const now = admin4.firestore.Timestamp.now();
|
|
1265
|
+
const calendarEventsQuery = this.db.collectionGroup(CALENDAR_SUBCOLLECTION_ID3).where("procedureId", "==", procedureId).where("eventTime.start", ">", now);
|
|
1266
|
+
try {
|
|
1267
|
+
const snapshot = await calendarEventsQuery.get();
|
|
1268
|
+
if (snapshot.empty) {
|
|
1269
|
+
console.log(
|
|
1270
|
+
`[ProcedureAggregationService] No upcoming calendar events found for procedure ${procedureId}. No procedure info updates needed.`
|
|
1271
|
+
);
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const batch = this.db.batch();
|
|
1275
|
+
snapshot.docs.forEach((doc) => {
|
|
1276
|
+
console.log(
|
|
1277
|
+
`[ProcedureAggregationService] Updating procedure info for calendar event ${doc.ref.path}`
|
|
1278
|
+
);
|
|
1279
|
+
batch.update(doc.ref, {
|
|
1280
|
+
procedureInfo,
|
|
1281
|
+
updatedAt: admin4.firestore.FieldValue.serverTimestamp()
|
|
1282
|
+
});
|
|
1283
|
+
});
|
|
1284
|
+
await batch.commit();
|
|
1285
|
+
console.log(
|
|
1286
|
+
`[ProcedureAggregationService] Successfully updated procedure info in ${snapshot.size} upcoming calendar events for procedure ${procedureId}.`
|
|
1287
|
+
);
|
|
1288
|
+
} catch (error) {
|
|
1289
|
+
console.error(
|
|
1290
|
+
`[ProcedureAggregationService] Error updating procedure info in calendar events for procedure ${procedureId}:`,
|
|
1291
|
+
error
|
|
1292
|
+
);
|
|
1293
|
+
throw error;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* Cancels all upcoming calendar events for a procedure
|
|
1298
|
+
* @param procedureId - ID of the procedure
|
|
1299
|
+
* @returns {Promise<void>}
|
|
1300
|
+
*/
|
|
1301
|
+
async cancelUpcomingCalendarEventsForProcedure(procedureId) {
|
|
1302
|
+
if (!procedureId) {
|
|
1303
|
+
console.log(
|
|
1304
|
+
"[ProcedureAggregationService] Missing procedureId for canceling calendar events. Skipping."
|
|
1305
|
+
);
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
console.log(
|
|
1309
|
+
`[ProcedureAggregationService] Querying upcoming calendar events for procedure ${procedureId} to cancel.`
|
|
1310
|
+
);
|
|
1311
|
+
const now = admin4.firestore.Timestamp.now();
|
|
1312
|
+
const calendarEventsQuery = this.db.collectionGroup(CALENDAR_SUBCOLLECTION_ID3).where("procedureId", "==", procedureId).where("eventTime.start", ">", now);
|
|
1313
|
+
try {
|
|
1314
|
+
const snapshot = await calendarEventsQuery.get();
|
|
1315
|
+
if (snapshot.empty) {
|
|
1316
|
+
console.log(
|
|
1317
|
+
`[ProcedureAggregationService] No upcoming calendar events found for procedure ${procedureId}. No events to cancel.`
|
|
1318
|
+
);
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
const batch = this.db.batch();
|
|
1322
|
+
snapshot.docs.forEach((doc) => {
|
|
1323
|
+
console.log(
|
|
1324
|
+
`[ProcedureAggregationService] Canceling calendar event ${doc.ref.path}`
|
|
1325
|
+
);
|
|
1326
|
+
batch.update(doc.ref, {
|
|
1327
|
+
status: "CANCELED",
|
|
1328
|
+
cancelReason: "Procedure deleted or inactivated",
|
|
1329
|
+
updatedAt: admin4.firestore.FieldValue.serverTimestamp()
|
|
1330
|
+
});
|
|
1331
|
+
});
|
|
1332
|
+
await batch.commit();
|
|
1333
|
+
console.log(
|
|
1334
|
+
`[ProcedureAggregationService] Successfully canceled ${snapshot.size} upcoming calendar events for procedure ${procedureId}.`
|
|
1335
|
+
);
|
|
1336
|
+
} catch (error) {
|
|
1337
|
+
console.error(
|
|
1338
|
+
`[ProcedureAggregationService] Error canceling calendar events for procedure ${procedureId}:`,
|
|
1339
|
+
error
|
|
1340
|
+
);
|
|
1341
|
+
throw error;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Removes procedure from a practitioner when a procedure is deleted or inactivated
|
|
1346
|
+
* @param practitionerId - ID of the practitioner who performs the procedure
|
|
1347
|
+
* @param procedureId - ID of the procedure
|
|
1348
|
+
* @returns {Promise<void>}
|
|
1349
|
+
*/
|
|
1350
|
+
async removeProcedureFromPractitioner(practitionerId, procedureId) {
|
|
1351
|
+
if (!practitionerId || !procedureId) {
|
|
1352
|
+
console.log(
|
|
1353
|
+
"[ProcedureAggregationService] Missing practitionerId or procedureId for removing procedure from practitioner. Skipping."
|
|
1354
|
+
);
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
console.log(
|
|
1358
|
+
`[ProcedureAggregationService] Removing procedure ${procedureId} from practitioner ${practitionerId}.`
|
|
1359
|
+
);
|
|
1360
|
+
const practitionerRef = this.db.collection(PRACTITIONERS_COLLECTION).doc(practitionerId);
|
|
1361
|
+
try {
|
|
1362
|
+
await this.db.runTransaction(async (transaction) => {
|
|
1363
|
+
const practitionerDoc = await transaction.get(practitionerRef);
|
|
1364
|
+
if (!practitionerDoc.exists) {
|
|
1365
|
+
throw new Error(
|
|
1366
|
+
`Practitioner ${practitionerId} does not exist for procedure removal`
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
const practitionerData = practitionerDoc.data();
|
|
1370
|
+
if (!practitionerData) {
|
|
1371
|
+
throw new Error(
|
|
1372
|
+
`Practitioner ${practitionerId} data is empty for procedure removal`
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
const proceduresInfo = practitionerData.proceduresInfo || [];
|
|
1376
|
+
const updatedProceduresInfo = proceduresInfo.filter(
|
|
1377
|
+
(p) => p.id !== procedureId
|
|
1378
|
+
);
|
|
1379
|
+
transaction.update(practitionerRef, {
|
|
1380
|
+
procedureIds: admin4.firestore.FieldValue.arrayRemove(procedureId),
|
|
1381
|
+
proceduresInfo: updatedProceduresInfo,
|
|
1382
|
+
updatedAt: admin4.firestore.FieldValue.serverTimestamp()
|
|
1383
|
+
});
|
|
1384
|
+
});
|
|
1385
|
+
console.log(
|
|
1386
|
+
`[ProcedureAggregationService] Successfully removed procedure ${procedureId} from practitioner ${practitionerId}.`
|
|
1387
|
+
);
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
console.error(
|
|
1390
|
+
`[ProcedureAggregationService] Error removing procedure ${procedureId} from practitioner ${practitionerId}:`,
|
|
1391
|
+
error
|
|
1392
|
+
);
|
|
1393
|
+
throw error;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* Removes procedure from a clinic when a procedure is deleted or inactivated
|
|
1398
|
+
* @param clinicId - ID of the clinic where the procedure is performed
|
|
1399
|
+
* @param procedureId - ID of the procedure
|
|
1400
|
+
* @returns {Promise<void>}
|
|
1401
|
+
*/
|
|
1402
|
+
async removeProcedureFromClinic(clinicId, procedureId) {
|
|
1403
|
+
if (!clinicId || !procedureId) {
|
|
1404
|
+
console.log(
|
|
1405
|
+
"[ProcedureAggregationService] Missing clinicId or procedureId for removing procedure from clinic. Skipping."
|
|
1406
|
+
);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
console.log(
|
|
1410
|
+
`[ProcedureAggregationService] Removing procedure ${procedureId} from clinic ${clinicId}.`
|
|
1411
|
+
);
|
|
1412
|
+
const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(clinicId);
|
|
1413
|
+
try {
|
|
1414
|
+
await this.db.runTransaction(async (transaction) => {
|
|
1415
|
+
const clinicDoc = await transaction.get(clinicRef);
|
|
1416
|
+
if (!clinicDoc.exists) {
|
|
1417
|
+
throw new Error(
|
|
1418
|
+
`Clinic ${clinicId} does not exist for procedure removal`
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
const clinicData = clinicDoc.data();
|
|
1422
|
+
if (!clinicData) {
|
|
1423
|
+
throw new Error(
|
|
1424
|
+
`Clinic ${clinicId} data is empty for procedure removal`
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
const proceduresInfo = clinicData.proceduresInfo || [];
|
|
1428
|
+
const updatedProceduresInfo = proceduresInfo.filter(
|
|
1429
|
+
(p) => p.id !== procedureId
|
|
1430
|
+
);
|
|
1431
|
+
transaction.update(clinicRef, {
|
|
1432
|
+
procedures: admin4.firestore.FieldValue.arrayRemove(procedureId),
|
|
1433
|
+
proceduresInfo: updatedProceduresInfo,
|
|
1434
|
+
updatedAt: admin4.firestore.FieldValue.serverTimestamp()
|
|
1435
|
+
});
|
|
1436
|
+
});
|
|
1437
|
+
console.log(
|
|
1438
|
+
`[ProcedureAggregationService] Successfully removed procedure ${procedureId} from clinic ${clinicId}.`
|
|
1439
|
+
);
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
console.error(
|
|
1442
|
+
`[ProcedureAggregationService] Error removing procedure ${procedureId} from clinic ${clinicId}:`,
|
|
1443
|
+
error
|
|
1444
|
+
);
|
|
1445
|
+
throw error;
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
|
|
1450
|
+
// src/admin/aggregation/patient/patient.aggregation.service.ts
|
|
1451
|
+
var admin5 = __toESM(require("firebase-admin"));
|
|
1452
|
+
var CALENDAR_SUBCOLLECTION_ID4 = "calendar";
|
|
1453
|
+
var PatientAggregationService = class {
|
|
1454
|
+
constructor(firestore7) {
|
|
1455
|
+
this.db = firestore7 || admin5.firestore();
|
|
1456
|
+
}
|
|
1457
|
+
// --- Methods for Patient Creation --- >
|
|
1458
|
+
// No specific aggregations defined for patient creation in the plan.
|
|
1459
|
+
// --- Methods for Patient Update --- >
|
|
1460
|
+
/**
|
|
1461
|
+
* Updates patient information in calendar events
|
|
1462
|
+
* @param patientId - ID of the patient
|
|
1463
|
+
* @param patientInfo - Updated patient information
|
|
1464
|
+
* @returns {Promise<void>}
|
|
1465
|
+
*/
|
|
1466
|
+
async updatePatientInfoInCalendarEvents(patientId, patientInfo) {
|
|
1467
|
+
if (!patientId || !patientInfo) {
|
|
1468
|
+
console.log(
|
|
1469
|
+
"[PatientAggregationService] Missing patientId or patientInfo for calendar update. Skipping."
|
|
1470
|
+
);
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
console.log(
|
|
1474
|
+
`[PatientAggregationService] Querying upcoming calendar events for patient ${patientId} to update patient info.`
|
|
1475
|
+
);
|
|
1476
|
+
const now = admin5.firestore.Timestamp.now();
|
|
1477
|
+
const calendarEventsQuery = this.db.collectionGroup(CALENDAR_SUBCOLLECTION_ID4).where("patientId", "==", patientId).where("eventTime.start", ">", now);
|
|
1478
|
+
try {
|
|
1479
|
+
const snapshot = await calendarEventsQuery.get();
|
|
1480
|
+
if (snapshot.empty) {
|
|
1481
|
+
console.log(
|
|
1482
|
+
`[PatientAggregationService] No upcoming calendar events found for patient ${patientId}. No patient info updates needed.`
|
|
1483
|
+
);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
const batch = this.db.batch();
|
|
1487
|
+
snapshot.docs.forEach((doc) => {
|
|
1488
|
+
console.log(
|
|
1489
|
+
`[PatientAggregationService] Updating patient info for calendar event ${doc.ref.path}`
|
|
1490
|
+
);
|
|
1491
|
+
batch.update(doc.ref, {
|
|
1492
|
+
patientInfo,
|
|
1493
|
+
updatedAt: admin5.firestore.FieldValue.serverTimestamp()
|
|
1494
|
+
});
|
|
1495
|
+
});
|
|
1496
|
+
await batch.commit();
|
|
1497
|
+
console.log(
|
|
1498
|
+
`[PatientAggregationService] Successfully updated patient info in ${snapshot.size} upcoming calendar events for patient ${patientId}.`
|
|
1499
|
+
);
|
|
1500
|
+
} catch (error) {
|
|
1501
|
+
console.error(
|
|
1502
|
+
`[PatientAggregationService] Error updating patient info in calendar events for patient ${patientId}:`,
|
|
1503
|
+
error
|
|
1504
|
+
);
|
|
1505
|
+
throw error;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
// --- Methods for Patient Deletion --- >
|
|
1509
|
+
/**
|
|
1510
|
+
* Cancels all upcoming calendar events associated with a deleted patient
|
|
1511
|
+
* @param patientId - ID of the deleted patient
|
|
1512
|
+
* @returns {Promise<void>}
|
|
1513
|
+
*/
|
|
1514
|
+
async cancelUpcomingCalendarEventsForPatient(patientId) {
|
|
1515
|
+
if (!patientId) {
|
|
1516
|
+
console.log(
|
|
1517
|
+
"[PatientAggregationService] Missing patientId for canceling calendar events. Skipping."
|
|
1518
|
+
);
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
console.log(
|
|
1522
|
+
`[PatientAggregationService] Querying upcoming calendar events for patient ${patientId} to cancel.`
|
|
1523
|
+
);
|
|
1524
|
+
const now = admin5.firestore.Timestamp.now();
|
|
1525
|
+
const calendarEventsQuery = this.db.collectionGroup(CALENDAR_SUBCOLLECTION_ID4).where("patientId", "==", patientId).where("eventTime.start", ">", now);
|
|
1526
|
+
try {
|
|
1527
|
+
const snapshot = await calendarEventsQuery.get();
|
|
1528
|
+
if (snapshot.empty) {
|
|
1529
|
+
console.log(
|
|
1530
|
+
`[PatientAggregationService] No upcoming calendar events found for patient ${patientId}. No events to cancel.`
|
|
1531
|
+
);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
const batch = this.db.batch();
|
|
1535
|
+
snapshot.docs.forEach((doc) => {
|
|
1536
|
+
console.log(
|
|
1537
|
+
`[PatientAggregationService] Canceling calendar event ${doc.ref.path}`
|
|
1538
|
+
);
|
|
1539
|
+
batch.update(doc.ref, {
|
|
1540
|
+
status: "CANCELED",
|
|
1541
|
+
cancelReason: "Patient deleted",
|
|
1542
|
+
updatedAt: admin5.firestore.FieldValue.serverTimestamp()
|
|
1543
|
+
});
|
|
1544
|
+
});
|
|
1545
|
+
await batch.commit();
|
|
1546
|
+
console.log(
|
|
1547
|
+
`[PatientAggregationService] Successfully canceled ${snapshot.size} upcoming calendar events for patient ${patientId}.`
|
|
1548
|
+
);
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
console.error(
|
|
1551
|
+
`[PatientAggregationService] Error canceling calendar events for patient ${patientId}:`,
|
|
1552
|
+
error
|
|
1553
|
+
);
|
|
1554
|
+
throw error;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1559
|
+
// src/admin/mailing/base.mailing.service.ts
|
|
1560
|
+
var admin6 = __toESM(require("firebase-admin"));
|
|
1561
|
+
var BaseMailingService = class {
|
|
1562
|
+
// Removed config property as it's no longer managed here
|
|
1563
|
+
// protected config: MailgunConfig;
|
|
1564
|
+
/**
|
|
1565
|
+
* Constructor for BaseMailingService
|
|
1566
|
+
* @param firestore Firestore instance provided by the caller
|
|
1567
|
+
* @param mailgunClient Mailgun client instance provided by the caller
|
|
1568
|
+
*/
|
|
1569
|
+
constructor(firestore7, mailgunClient) {
|
|
1570
|
+
this.db = firestore7;
|
|
1571
|
+
this.mailgunClient = mailgunClient;
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* Sends an email using Mailgun
|
|
1575
|
+
* @param data Email data to send, including the 'from' address
|
|
1576
|
+
* @returns Promise with the sending result
|
|
1577
|
+
*/
|
|
1578
|
+
async sendEmail(data) {
|
|
1579
|
+
try {
|
|
1580
|
+
if (!data.from) {
|
|
1581
|
+
throw new Error(
|
|
1582
|
+
"Email 'from' address must be provided in sendEmail data."
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
return await new Promise(
|
|
1586
|
+
(resolve, reject) => {
|
|
1587
|
+
this.mailgunClient.messages().send(data, (error, body) => {
|
|
1588
|
+
if (error) {
|
|
1589
|
+
console.error("[BaseMailingService] Error sending email:", error);
|
|
1590
|
+
reject(error);
|
|
1591
|
+
} else {
|
|
1592
|
+
console.log(
|
|
1593
|
+
"[BaseMailingService] Email sent successfully:",
|
|
1594
|
+
body
|
|
1595
|
+
);
|
|
1596
|
+
resolve(body);
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
);
|
|
1601
|
+
} catch (error) {
|
|
1602
|
+
console.error("[BaseMailingService] Error in sendEmail:", error);
|
|
1603
|
+
throw error;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Logs email sending attempt to Firestore for tracking
|
|
1608
|
+
* @param emailData Email data that was sent
|
|
1609
|
+
* @param success Whether the email was sent successfully
|
|
1610
|
+
* @param error Error object if the email failed to send
|
|
1611
|
+
*/
|
|
1612
|
+
async logEmailAttempt(emailData, success, error) {
|
|
1613
|
+
try {
|
|
1614
|
+
const emailLogRef = this.db.collection("email_logs").doc();
|
|
1615
|
+
await emailLogRef.set({
|
|
1616
|
+
to: emailData.to,
|
|
1617
|
+
subject: emailData.subject,
|
|
1618
|
+
templateName: emailData.templateName,
|
|
1619
|
+
success,
|
|
1620
|
+
error: error ? JSON.stringify(error) : null,
|
|
1621
|
+
sentAt: admin6.firestore.FieldValue.serverTimestamp()
|
|
1622
|
+
});
|
|
1623
|
+
} catch (logError) {
|
|
1624
|
+
console.error(
|
|
1625
|
+
"[BaseMailingService] Error logging email attempt:",
|
|
1626
|
+
logError
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Renders a simple HTML email template with variables
|
|
1632
|
+
* @param template HTML template string
|
|
1633
|
+
* @param variables Key-value pairs to replace in the template
|
|
1634
|
+
* @returns Rendered HTML string
|
|
1635
|
+
*/
|
|
1636
|
+
renderTemplate(template, variables) {
|
|
1637
|
+
let rendered = template;
|
|
1638
|
+
Object.entries(variables).forEach(([key, value]) => {
|
|
1639
|
+
const regex = new RegExp(`{{\\s*${key}\\s*}}`, "g");
|
|
1640
|
+
rendered = rendered.replace(regex, value);
|
|
1641
|
+
});
|
|
1642
|
+
return rendered;
|
|
1643
|
+
}
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
// src/admin/mailing/practitionerInvite/templates/invitation.template.ts
|
|
1647
|
+
var practitionerInvitationTemplate = `
|
|
1648
|
+
<!DOCTYPE html>
|
|
1649
|
+
<html>
|
|
1650
|
+
<head>
|
|
1651
|
+
<meta charset="UTF-8">
|
|
1652
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1653
|
+
<title>Join {{clinicName}} as a Practitioner</title>
|
|
1654
|
+
<style>
|
|
1655
|
+
body {
|
|
1656
|
+
font-family: Arial, sans-serif;
|
|
1657
|
+
line-height: 1.6;
|
|
1658
|
+
color: #333;
|
|
1659
|
+
margin: 0;
|
|
1660
|
+
padding: 0;
|
|
1661
|
+
}
|
|
1662
|
+
.container {
|
|
1663
|
+
max-width: 600px;
|
|
1664
|
+
margin: 0 auto;
|
|
1665
|
+
padding: 20px;
|
|
1666
|
+
}
|
|
1667
|
+
.header {
|
|
1668
|
+
background-color: #4A90E2;
|
|
1669
|
+
padding: 20px;
|
|
1670
|
+
text-align: center;
|
|
1671
|
+
color: white;
|
|
1672
|
+
}
|
|
1673
|
+
.content {
|
|
1674
|
+
padding: 20px;
|
|
1675
|
+
background-color: #f9f9f9;
|
|
1676
|
+
}
|
|
1677
|
+
.footer {
|
|
1678
|
+
padding: 20px;
|
|
1679
|
+
text-align: center;
|
|
1680
|
+
font-size: 12px;
|
|
1681
|
+
color: #888;
|
|
1682
|
+
}
|
|
1683
|
+
.button {
|
|
1684
|
+
display: inline-block;
|
|
1685
|
+
background-color: #4A90E2;
|
|
1686
|
+
color: white;
|
|
1687
|
+
text-decoration: none;
|
|
1688
|
+
padding: 12px 24px;
|
|
1689
|
+
border-radius: 4px;
|
|
1690
|
+
margin: 20px 0;
|
|
1691
|
+
font-weight: bold;
|
|
1692
|
+
}
|
|
1693
|
+
.token {
|
|
1694
|
+
font-size: 24px;
|
|
1695
|
+
font-weight: bold;
|
|
1696
|
+
color: #4A90E2;
|
|
1697
|
+
padding: 10px;
|
|
1698
|
+
background-color: #e9f0f9;
|
|
1699
|
+
border-radius: 4px;
|
|
1700
|
+
display: inline-block;
|
|
1701
|
+
letter-spacing: 2px;
|
|
1702
|
+
margin: 10px 0;
|
|
1703
|
+
}
|
|
1704
|
+
</style>
|
|
1705
|
+
</head>
|
|
1706
|
+
<body>
|
|
1707
|
+
<div class="container">
|
|
1708
|
+
<div class="header">
|
|
1709
|
+
<h1>You've Been Invited</h1>
|
|
1710
|
+
</div>
|
|
1711
|
+
<div class="content">
|
|
1712
|
+
<p>Hello {{practitionerName}},</p>
|
|
1713
|
+
|
|
1714
|
+
<p>You have been invited to join <strong>{{clinicName}}</strong> as a healthcare practitioner.</p>
|
|
1715
|
+
|
|
1716
|
+
<p>Your profile has been created and is ready for you to claim. Please use the following token to register:</p>
|
|
1717
|
+
|
|
1718
|
+
<div style="text-align: center;">
|
|
1719
|
+
<span class="token">{{inviteToken}}</span>
|
|
1720
|
+
</div>
|
|
1721
|
+
|
|
1722
|
+
<p>This token will expire on <strong>{{expirationDate}}</strong>.</p>
|
|
1723
|
+
|
|
1724
|
+
<p>To create your account:</p>
|
|
1725
|
+
<ol>
|
|
1726
|
+
<li>Visit {{registrationUrl}}</li>
|
|
1727
|
+
<li>Enter your email and create a password</li>
|
|
1728
|
+
<li>When prompted, enter the token above</li>
|
|
1729
|
+
</ol>
|
|
1730
|
+
|
|
1731
|
+
<div style="text-align: center;">
|
|
1732
|
+
<a href="{{registrationUrl}}" class="button">Create Your Account</a>
|
|
1733
|
+
</div>
|
|
1734
|
+
|
|
1735
|
+
<p>If you have any questions, please contact {{contactName}} at {{contactEmail}}.</p>
|
|
1736
|
+
</div>
|
|
1737
|
+
<div class="footer">
|
|
1738
|
+
<p>This is an automated message from {{clinicName}}. Please do not reply to this email.</p>
|
|
1739
|
+
<p>© {{currentYear}} {{clinicName}}. All rights reserved.</p>
|
|
1740
|
+
</div>
|
|
1741
|
+
</div>
|
|
1742
|
+
</body>
|
|
1743
|
+
</html>
|
|
1744
|
+
`;
|
|
1745
|
+
|
|
1746
|
+
// src/admin/mailing/practitionerInvite/practitionerInvite.mailing.ts
|
|
1747
|
+
var PractitionerInviteMailingService = class extends BaseMailingService {
|
|
1748
|
+
/**
|
|
1749
|
+
* Constructor for PractitionerInviteMailingService
|
|
1750
|
+
* @param firestore Firestore instance provided by the caller
|
|
1751
|
+
* @param mailgunClient Mailgun client instance provided by the caller
|
|
1752
|
+
*/
|
|
1753
|
+
constructor(firestore7, mailgunClient) {
|
|
1754
|
+
super(firestore7, mailgunClient);
|
|
1755
|
+
this.DEFAULT_REGISTRATION_URL = "https://app.medclinic.com/register";
|
|
1756
|
+
this.DEFAULT_SUBJECT = "You've Been Invited to Join as a Practitioner";
|
|
1757
|
+
this.DEFAULT_FROM_ADDRESS = "MedClinic <no-reply@your-domain.com>";
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Sends a practitioner invitation email
|
|
1761
|
+
* @param data The practitioner invitation data
|
|
1762
|
+
* @returns Promise resolved when email is sent
|
|
1763
|
+
*/
|
|
1764
|
+
async sendInvitationEmail(data) {
|
|
1765
|
+
var _a, _b, _c, _d;
|
|
1766
|
+
try {
|
|
1767
|
+
console.log(
|
|
1768
|
+
"[PractitionerInviteMailingService] Sending invitation email to",
|
|
1769
|
+
data.token.email
|
|
1770
|
+
);
|
|
1771
|
+
const expirationDate = data.token.expiresAt.toDate().toLocaleDateString("en-US", {
|
|
1772
|
+
weekday: "long",
|
|
1773
|
+
year: "numeric",
|
|
1774
|
+
month: "long",
|
|
1775
|
+
day: "numeric"
|
|
1776
|
+
});
|
|
1777
|
+
const registrationUrl = ((_a = data.options) == null ? void 0 : _a.registrationUrl) || this.DEFAULT_REGISTRATION_URL;
|
|
1778
|
+
const contactName = data.clinic.contactName || "Clinic Administrator";
|
|
1779
|
+
const contactEmail = data.clinic.contactEmail;
|
|
1780
|
+
const subject = ((_b = data.options) == null ? void 0 : _b.customSubject) || this.DEFAULT_SUBJECT;
|
|
1781
|
+
const fromAddress = ((_c = data.options) == null ? void 0 : _c.fromAddress) || this.DEFAULT_FROM_ADDRESS;
|
|
1782
|
+
const currentYear = (/* @__PURE__ */ new Date()).getFullYear().toString();
|
|
1783
|
+
const practitionerName = `${data.practitioner.firstName} ${data.practitioner.lastName}`;
|
|
1784
|
+
const templateVariables = {
|
|
1785
|
+
clinicName: data.clinic.name,
|
|
1786
|
+
practitionerName,
|
|
1787
|
+
inviteToken: data.token.token,
|
|
1788
|
+
expirationDate,
|
|
1789
|
+
registrationUrl,
|
|
1790
|
+
contactName,
|
|
1791
|
+
contactEmail,
|
|
1792
|
+
currentYear
|
|
1793
|
+
};
|
|
1794
|
+
const html = this.renderTemplate(
|
|
1795
|
+
practitionerInvitationTemplate,
|
|
1796
|
+
templateVariables
|
|
1797
|
+
);
|
|
1798
|
+
const emailData = {
|
|
1799
|
+
to: data.token.email,
|
|
1800
|
+
from: fromAddress,
|
|
1801
|
+
subject,
|
|
1802
|
+
html
|
|
1803
|
+
};
|
|
1804
|
+
const result = await this.sendEmail(emailData);
|
|
1805
|
+
await this.logEmailAttempt(
|
|
1806
|
+
{
|
|
1807
|
+
to: data.token.email,
|
|
1808
|
+
subject,
|
|
1809
|
+
templateName: "practitioner_invitation"
|
|
1810
|
+
},
|
|
1811
|
+
true
|
|
1812
|
+
);
|
|
1813
|
+
return result;
|
|
1814
|
+
} catch (error) {
|
|
1815
|
+
console.error(
|
|
1816
|
+
"[PractitionerInviteMailingService] Error sending invitation email:",
|
|
1817
|
+
error
|
|
1818
|
+
);
|
|
1819
|
+
await this.logEmailAttempt(
|
|
1820
|
+
{
|
|
1821
|
+
to: data.token.email,
|
|
1822
|
+
subject: ((_d = data.options) == null ? void 0 : _d.customSubject) || this.DEFAULT_SUBJECT,
|
|
1823
|
+
templateName: "practitioner_invitation"
|
|
1824
|
+
},
|
|
1825
|
+
false,
|
|
1826
|
+
error
|
|
1827
|
+
);
|
|
1828
|
+
throw error;
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Handles the practitioner token creation event from Cloud Functions
|
|
1833
|
+
* Fetches necessary data using defined types and collection constants,
|
|
1834
|
+
* and sends the invitation email.
|
|
1835
|
+
* @param tokenData The fully typed token object including its id
|
|
1836
|
+
* @param fromAddress The 'from' email address to use, obtained from config
|
|
1837
|
+
* @returns Promise resolved when the email is sent
|
|
1838
|
+
*/
|
|
1839
|
+
async handleTokenCreationEvent(tokenData, fromAddress) {
|
|
1840
|
+
try {
|
|
1841
|
+
console.log(
|
|
1842
|
+
"[PractitionerInviteMailingService] Handling token creation event for token:",
|
|
1843
|
+
tokenData.id
|
|
1844
|
+
);
|
|
1845
|
+
const practitionerRef = this.db.collection(PRACTITIONERS_COLLECTION).doc(tokenData.practitionerId);
|
|
1846
|
+
const practitionerDoc = await practitionerRef.get();
|
|
1847
|
+
if (!practitionerDoc.exists) {
|
|
1848
|
+
throw new Error(`Practitioner ${tokenData.practitionerId} not found`);
|
|
1849
|
+
}
|
|
1850
|
+
const practitionerData = practitionerDoc.data();
|
|
1851
|
+
const clinicRef = this.db.collection(CLINICS_COLLECTION).doc(tokenData.clinicId);
|
|
1852
|
+
const clinicDoc = await clinicRef.get();
|
|
1853
|
+
if (!clinicDoc.exists) {
|
|
1854
|
+
throw new Error(`Clinic ${tokenData.clinicId} not found`);
|
|
1855
|
+
}
|
|
1856
|
+
const clinicData = clinicDoc.data();
|
|
1857
|
+
const emailData = {
|
|
1858
|
+
token: {
|
|
1859
|
+
id: tokenData.id,
|
|
1860
|
+
token: tokenData.token,
|
|
1861
|
+
practitionerId: tokenData.practitionerId,
|
|
1862
|
+
email: tokenData.email,
|
|
1863
|
+
clinicId: tokenData.clinicId,
|
|
1864
|
+
expiresAt: tokenData.expiresAt
|
|
1865
|
+
},
|
|
1866
|
+
practitioner: {
|
|
1867
|
+
firstName: practitionerData.basicInfo.firstName || "",
|
|
1868
|
+
lastName: practitionerData.basicInfo.lastName || ""
|
|
1869
|
+
},
|
|
1870
|
+
clinic: {
|
|
1871
|
+
name: clinicData.name || "Medical Clinic",
|
|
1872
|
+
contactEmail: clinicData.contactInfo.email || "contact@medclinic.com"
|
|
1873
|
+
},
|
|
1874
|
+
options: {
|
|
1875
|
+
fromAddress
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
await this.sendInvitationEmail(emailData);
|
|
1879
|
+
console.log(
|
|
1880
|
+
"[PractitionerInviteMailingService] Invitation email sent successfully"
|
|
1881
|
+
);
|
|
1882
|
+
} catch (error) {
|
|
1883
|
+
console.error(
|
|
1884
|
+
"[PractitionerInviteMailingService] Error handling token creation event:",
|
|
1885
|
+
error
|
|
1886
|
+
);
|
|
1887
|
+
throw error;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
};
|
|
1891
|
+
|
|
229
1892
|
// src/types/index.ts
|
|
230
1893
|
var UserRole = /* @__PURE__ */ ((UserRole2) => {
|
|
231
1894
|
UserRole2["PATIENT"] = "patient";
|
|
@@ -234,11 +1897,20 @@ var UserRole = /* @__PURE__ */ ((UserRole2) => {
|
|
|
234
1897
|
UserRole2["CLINIC_ADMIN"] = "clinic_admin";
|
|
235
1898
|
return UserRole2;
|
|
236
1899
|
})(UserRole || {});
|
|
1900
|
+
|
|
1901
|
+
// src/admin/index.ts
|
|
1902
|
+
console.log("[Admin Module] Initialized and services exported.");
|
|
237
1903
|
// Annotate the CommonJS export names for ESM import in node:
|
|
238
1904
|
0 && (module.exports = {
|
|
1905
|
+
BaseMailingService,
|
|
1906
|
+
ClinicAggregationService,
|
|
239
1907
|
NOTIFICATIONS_COLLECTION,
|
|
240
1908
|
NotificationStatus,
|
|
241
1909
|
NotificationType,
|
|
242
1910
|
NotificationsAdmin,
|
|
1911
|
+
PatientAggregationService,
|
|
1912
|
+
PractitionerAggregationService,
|
|
1913
|
+
PractitionerInviteMailingService,
|
|
1914
|
+
ProcedureAggregationService,
|
|
243
1915
|
UserRole
|
|
244
1916
|
});
|