@blackcode_sa/metaestetics-api 1.7.33 → 1.7.35
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 +244 -57
- package/dist/admin/index.d.ts +244 -57
- package/dist/admin/index.js +1281 -7
- package/dist/admin/index.mjs +1280 -7
- package/package.json +1 -1
- package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts +398 -13
- package/src/admin/index.ts +12 -1
- package/src/admin/mailing/practitionerInvite/existing-practitioner-invite.mailing.ts +611 -0
- package/src/admin/mailing/practitionerInvite/templates/existing-practitioner-invitation.template.ts +155 -0
- package/src/admin/mailing/practitionerInvite/templates/invite-accepted-notification.template.ts +228 -0
- package/src/admin/mailing/practitionerInvite/templates/invite-rejected-notification.template.ts +242 -0
package/package.json
CHANGED
package/src/admin/aggregation/practitioner-invite/practitioner-invite.aggregation.service.ts
CHANGED
|
@@ -10,9 +10,15 @@ import {
|
|
|
10
10
|
PRACTITIONERS_COLLECTION,
|
|
11
11
|
PractitionerClinicWorkingHours,
|
|
12
12
|
} from "../../../types/practitioner";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
CLINICS_COLLECTION,
|
|
15
|
+
Clinic,
|
|
16
|
+
ClinicAdmin,
|
|
17
|
+
CLINIC_ADMINS_COLLECTION,
|
|
18
|
+
} from "../../../types/clinic";
|
|
14
19
|
import { ClinicInfo } from "../../../types/profile";
|
|
15
20
|
import { Logger } from "../../logger";
|
|
21
|
+
import { ExistingPractitionerInviteMailingService } from "../../mailing/practitionerInvite/existing-practitioner-invite.mailing";
|
|
16
22
|
|
|
17
23
|
/**
|
|
18
24
|
* @class PractitionerInviteAggregationService
|
|
@@ -22,13 +28,19 @@ import { Logger } from "../../logger";
|
|
|
22
28
|
*/
|
|
23
29
|
export class PractitionerInviteAggregationService {
|
|
24
30
|
private db: admin.firestore.Firestore;
|
|
31
|
+
private mailingService?: ExistingPractitionerInviteMailingService;
|
|
25
32
|
|
|
26
33
|
/**
|
|
27
34
|
* Constructor for PractitionerInviteAggregationService.
|
|
28
35
|
* @param firestore Optional Firestore instance. If not provided, it uses the default admin SDK instance.
|
|
36
|
+
* @param mailingService Optional mailing service for sending emails
|
|
29
37
|
*/
|
|
30
|
-
constructor(
|
|
38
|
+
constructor(
|
|
39
|
+
firestore?: admin.firestore.Firestore,
|
|
40
|
+
mailingService?: ExistingPractitionerInviteMailingService
|
|
41
|
+
) {
|
|
31
42
|
this.db = firestore || admin.firestore();
|
|
43
|
+
this.mailingService = mailingService;
|
|
32
44
|
Logger.info("[PractitionerInviteAggregationService] Initialized.");
|
|
33
45
|
}
|
|
34
46
|
|
|
@@ -36,17 +48,49 @@ export class PractitionerInviteAggregationService {
|
|
|
36
48
|
* Handles side effects when a practitioner invite is first created.
|
|
37
49
|
* This function would typically be called by a Firestore onCreate trigger.
|
|
38
50
|
* @param {PractitionerInvite} invite - The newly created PractitionerInvite object.
|
|
51
|
+
* @param {object} emailConfig - Optional email configuration for sending invite emails
|
|
39
52
|
* @returns {Promise<void>}
|
|
40
53
|
*/
|
|
41
|
-
async handleInviteCreate(
|
|
54
|
+
async handleInviteCreate(
|
|
55
|
+
invite: PractitionerInvite,
|
|
56
|
+
emailConfig?: {
|
|
57
|
+
fromAddress: string;
|
|
58
|
+
domain: string;
|
|
59
|
+
acceptUrl: string;
|
|
60
|
+
rejectUrl: string;
|
|
61
|
+
}
|
|
62
|
+
): Promise<void> {
|
|
42
63
|
Logger.info(
|
|
43
64
|
`[PractitionerInviteAggService] Handling CREATE for invite: ${invite.id}, practitioner: ${invite.practitionerId}, clinic: ${invite.clinicId}, status: ${invite.status}`
|
|
44
65
|
);
|
|
45
66
|
|
|
46
67
|
try {
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
// Send invitation email to practitioner if mailing service is available
|
|
69
|
+
if (
|
|
70
|
+
this.mailingService &&
|
|
71
|
+
emailConfig &&
|
|
72
|
+
invite.status === PractitionerInviteStatus.PENDING
|
|
73
|
+
) {
|
|
74
|
+
Logger.info(
|
|
75
|
+
`[PractitionerInviteAggService] Sending invitation email for invite: ${invite.id}`
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await this.mailingService.handleInviteCreationEvent(
|
|
80
|
+
invite,
|
|
81
|
+
emailConfig
|
|
82
|
+
);
|
|
83
|
+
Logger.info(
|
|
84
|
+
`[PractitionerInviteAggService] Successfully sent invitation email for invite: ${invite.id}`
|
|
85
|
+
);
|
|
86
|
+
} catch (emailError) {
|
|
87
|
+
Logger.error(
|
|
88
|
+
`[PractitionerInviteAggService] Error sending invitation email for invite ${invite.id}:`,
|
|
89
|
+
emailError
|
|
90
|
+
);
|
|
91
|
+
// Don't throw - email failure shouldn't break the invite creation
|
|
92
|
+
}
|
|
93
|
+
}
|
|
50
94
|
|
|
51
95
|
Logger.info(
|
|
52
96
|
`[PractitionerInviteAggService] Successfully processed CREATE for invite: ${invite.id}`
|
|
@@ -65,11 +109,19 @@ export class PractitionerInviteAggregationService {
|
|
|
65
109
|
* This function would typically be called by a Firestore onUpdate trigger.
|
|
66
110
|
* @param {PractitionerInvite} before - The PractitionerInvite object before the update.
|
|
67
111
|
* @param {PractitionerInvite} after - The PractitionerInvite object after the update.
|
|
112
|
+
* @param {object} emailConfig - Optional email configuration for sending notification emails
|
|
68
113
|
* @returns {Promise<void>}
|
|
69
114
|
*/
|
|
70
115
|
async handleInviteUpdate(
|
|
71
116
|
before: PractitionerInvite,
|
|
72
|
-
after: PractitionerInvite
|
|
117
|
+
after: PractitionerInvite,
|
|
118
|
+
emailConfig?: {
|
|
119
|
+
fromAddress: string;
|
|
120
|
+
domain: string;
|
|
121
|
+
clinicDashboardUrl: string;
|
|
122
|
+
practitionerProfileUrl?: string;
|
|
123
|
+
findPractitionersUrl?: string;
|
|
124
|
+
}
|
|
73
125
|
): Promise<void> {
|
|
74
126
|
Logger.info(
|
|
75
127
|
`[PractitionerInviteAggService] Handling UPDATE for invite: ${after.id}. Status ${before.status} -> ${after.status}`
|
|
@@ -91,7 +143,7 @@ export class PractitionerInviteAggregationService {
|
|
|
91
143
|
Logger.info(
|
|
92
144
|
`[PractitionerInviteAggService] Invite ${after.id} PENDING -> ACCEPTED. Adding practitioner to clinic.`
|
|
93
145
|
);
|
|
94
|
-
await this.handleInviteAccepted(after);
|
|
146
|
+
await this.handleInviteAccepted(after, emailConfig);
|
|
95
147
|
}
|
|
96
148
|
|
|
97
149
|
// Handle PENDING -> REJECTED
|
|
@@ -102,7 +154,7 @@ export class PractitionerInviteAggregationService {
|
|
|
102
154
|
Logger.info(
|
|
103
155
|
`[PractitionerInviteAggService] Invite ${after.id} PENDING -> REJECTED.`
|
|
104
156
|
);
|
|
105
|
-
await this.handleInviteRejected(after);
|
|
157
|
+
await this.handleInviteRejected(after, emailConfig);
|
|
106
158
|
}
|
|
107
159
|
|
|
108
160
|
// Handle PENDING -> CANCELLED
|
|
@@ -162,10 +214,17 @@ export class PractitionerInviteAggregationService {
|
|
|
162
214
|
* Handles the business logic when a practitioner accepts an invite.
|
|
163
215
|
* This includes adding the practitioner to the clinic and the clinic to the practitioner.
|
|
164
216
|
* @param {PractitionerInvite} invite - The accepted invite
|
|
217
|
+
* @param {object} emailConfig - Optional email configuration for sending notification emails
|
|
165
218
|
* @returns {Promise<void>}
|
|
166
219
|
*/
|
|
167
220
|
private async handleInviteAccepted(
|
|
168
|
-
invite: PractitionerInvite
|
|
221
|
+
invite: PractitionerInvite,
|
|
222
|
+
emailConfig?: {
|
|
223
|
+
fromAddress: string;
|
|
224
|
+
domain: string;
|
|
225
|
+
clinicDashboardUrl: string;
|
|
226
|
+
practitionerProfileUrl?: string;
|
|
227
|
+
}
|
|
169
228
|
): Promise<void> {
|
|
170
229
|
Logger.info(
|
|
171
230
|
`[PractitionerInviteAggService] Processing accepted invite ${invite.id} for practitioner ${invite.practitionerId} and clinic ${invite.clinicId}`
|
|
@@ -251,6 +310,31 @@ export class PractitionerInviteAggregationService {
|
|
|
251
310
|
await this.updatePractitionerWorkingHours(practitioner.id, invite);
|
|
252
311
|
}
|
|
253
312
|
|
|
313
|
+
// Send acceptance notification email to clinic admin if mailing service is available
|
|
314
|
+
if (this.mailingService && emailConfig) {
|
|
315
|
+
Logger.info(
|
|
316
|
+
`[PractitionerInviteAggService] Sending acceptance notification email for invite: ${invite.id}`
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
await this.sendAcceptanceNotificationEmail(
|
|
321
|
+
invite,
|
|
322
|
+
practitioner,
|
|
323
|
+
clinic,
|
|
324
|
+
emailConfig
|
|
325
|
+
);
|
|
326
|
+
Logger.info(
|
|
327
|
+
`[PractitionerInviteAggService] Successfully sent acceptance notification email for invite: ${invite.id}`
|
|
328
|
+
);
|
|
329
|
+
} catch (emailError) {
|
|
330
|
+
Logger.error(
|
|
331
|
+
`[PractitionerInviteAggService] Error sending acceptance notification email for invite ${invite.id}:`,
|
|
332
|
+
emailError
|
|
333
|
+
);
|
|
334
|
+
// Don't throw - email failure shouldn't break the acceptance logic
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
254
338
|
Logger.info(
|
|
255
339
|
`[PractitionerInviteAggService] Successfully processed accepted invite ${invite.id}`
|
|
256
340
|
);
|
|
@@ -266,18 +350,54 @@ export class PractitionerInviteAggregationService {
|
|
|
266
350
|
/**
|
|
267
351
|
* Handles the business logic when a practitioner rejects an invite.
|
|
268
352
|
* @param {PractitionerInvite} invite - The rejected invite
|
|
353
|
+
* @param {object} emailConfig - Optional email configuration for sending notification emails
|
|
269
354
|
* @returns {Promise<void>}
|
|
270
355
|
*/
|
|
271
356
|
private async handleInviteRejected(
|
|
272
|
-
invite: PractitionerInvite
|
|
357
|
+
invite: PractitionerInvite,
|
|
358
|
+
emailConfig?: {
|
|
359
|
+
fromAddress: string;
|
|
360
|
+
domain: string;
|
|
361
|
+
clinicDashboardUrl: string;
|
|
362
|
+
findPractitionersUrl?: string;
|
|
363
|
+
}
|
|
273
364
|
): Promise<void> {
|
|
274
365
|
Logger.info(
|
|
275
366
|
`[PractitionerInviteAggService] Processing rejected invite ${invite.id}`
|
|
276
367
|
);
|
|
277
368
|
|
|
278
369
|
try {
|
|
279
|
-
//
|
|
280
|
-
|
|
370
|
+
// Send rejection notification email to clinic admin if mailing service is available
|
|
371
|
+
if (this.mailingService && emailConfig) {
|
|
372
|
+
Logger.info(
|
|
373
|
+
`[PractitionerInviteAggService] Sending rejection notification email for invite: ${invite.id}`
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const [practitioner, clinic] = await Promise.all([
|
|
378
|
+
this.fetchPractitionerById(invite.practitionerId),
|
|
379
|
+
this.fetchClinicById(invite.clinicId),
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
if (practitioner && clinic) {
|
|
383
|
+
await this.sendRejectionNotificationEmail(
|
|
384
|
+
invite,
|
|
385
|
+
practitioner,
|
|
386
|
+
clinic,
|
|
387
|
+
emailConfig
|
|
388
|
+
);
|
|
389
|
+
Logger.info(
|
|
390
|
+
`[PractitionerInviteAggService] Successfully sent rejection notification email for invite: ${invite.id}`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
} catch (emailError) {
|
|
394
|
+
Logger.error(
|
|
395
|
+
`[PractitionerInviteAggService] Error sending rejection notification email for invite ${invite.id}:`,
|
|
396
|
+
emailError
|
|
397
|
+
);
|
|
398
|
+
// Don't throw - email failure shouldn't break the rejection logic
|
|
399
|
+
}
|
|
400
|
+
}
|
|
281
401
|
|
|
282
402
|
Logger.info(
|
|
283
403
|
`[PractitionerInviteAggService] Successfully processed rejected invite ${invite.id}`
|
|
@@ -530,6 +650,29 @@ export class PractitionerInviteAggregationService {
|
|
|
530
650
|
|
|
531
651
|
// --- Data Fetching Helpers ---
|
|
532
652
|
|
|
653
|
+
/**
|
|
654
|
+
* Fetches a clinic admin by ID
|
|
655
|
+
* @param adminId The clinic admin ID
|
|
656
|
+
* @returns The clinic admin or null if not found
|
|
657
|
+
*/
|
|
658
|
+
private async fetchClinicAdminById(
|
|
659
|
+
adminId: string
|
|
660
|
+
): Promise<ClinicAdmin | null> {
|
|
661
|
+
try {
|
|
662
|
+
const doc = await this.db
|
|
663
|
+
.collection(CLINIC_ADMINS_COLLECTION)
|
|
664
|
+
.doc(adminId)
|
|
665
|
+
.get();
|
|
666
|
+
return doc.exists ? (doc.data() as ClinicAdmin) : null;
|
|
667
|
+
} catch (error) {
|
|
668
|
+
Logger.error(
|
|
669
|
+
`[PractitionerInviteAggService] Error fetching clinic admin ${adminId}:`,
|
|
670
|
+
error
|
|
671
|
+
);
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
533
676
|
/**
|
|
534
677
|
* Fetches a practitioner by ID.
|
|
535
678
|
* @param practitionerId The practitioner ID.
|
|
@@ -573,4 +716,246 @@ export class PractitionerInviteAggregationService {
|
|
|
573
716
|
return null;
|
|
574
717
|
}
|
|
575
718
|
}
|
|
719
|
+
|
|
720
|
+
// --- Email Helper Methods ---
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Sends acceptance notification email to clinic admin
|
|
724
|
+
* @param invite The accepted invite
|
|
725
|
+
* @param practitioner The practitioner who accepted
|
|
726
|
+
* @param clinic The clinic that sent the invite
|
|
727
|
+
* @param emailConfig Email configuration
|
|
728
|
+
*/
|
|
729
|
+
private async sendAcceptanceNotificationEmail(
|
|
730
|
+
invite: PractitionerInvite,
|
|
731
|
+
practitioner: Practitioner,
|
|
732
|
+
clinic: Clinic,
|
|
733
|
+
emailConfig: {
|
|
734
|
+
fromAddress: string;
|
|
735
|
+
domain: string;
|
|
736
|
+
clinicDashboardUrl: string;
|
|
737
|
+
practitionerProfileUrl?: string;
|
|
738
|
+
}
|
|
739
|
+
): Promise<void> {
|
|
740
|
+
if (!this.mailingService) return;
|
|
741
|
+
|
|
742
|
+
try {
|
|
743
|
+
// Fetch the admin who created the invite
|
|
744
|
+
const admin = await this.fetchClinicAdminById(invite.invitedBy);
|
|
745
|
+
if (!admin) {
|
|
746
|
+
Logger.warn(
|
|
747
|
+
`[PractitionerInviteAggService] Admin ${invite.invitedBy} not found, using clinic contact email as fallback`
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
// Fallback to clinic contact email
|
|
751
|
+
const notificationData = {
|
|
752
|
+
invite,
|
|
753
|
+
practitioner: {
|
|
754
|
+
firstName: practitioner.basicInfo.firstName || "",
|
|
755
|
+
lastName: practitioner.basicInfo.lastName || "",
|
|
756
|
+
specialties:
|
|
757
|
+
practitioner.certification?.specialties?.map(
|
|
758
|
+
(s: any) => s.name || s
|
|
759
|
+
) || [],
|
|
760
|
+
profileImageUrl:
|
|
761
|
+
typeof practitioner.basicInfo.profileImageUrl === "string"
|
|
762
|
+
? practitioner.basicInfo.profileImageUrl
|
|
763
|
+
: null,
|
|
764
|
+
experienceYears: undefined,
|
|
765
|
+
},
|
|
766
|
+
clinic: {
|
|
767
|
+
name: clinic.name,
|
|
768
|
+
adminName: "Admin",
|
|
769
|
+
adminEmail: clinic.contactInfo.email,
|
|
770
|
+
},
|
|
771
|
+
context: {
|
|
772
|
+
invitationDate: invite.createdAt.toDate().toLocaleDateString(),
|
|
773
|
+
responseDate:
|
|
774
|
+
invite.acceptedAt?.toDate().toLocaleDateString() ||
|
|
775
|
+
new Date().toLocaleDateString(),
|
|
776
|
+
},
|
|
777
|
+
urls: {
|
|
778
|
+
clinicDashboardUrl: emailConfig.clinicDashboardUrl,
|
|
779
|
+
practitionerProfileUrl: emailConfig.practitionerProfileUrl,
|
|
780
|
+
},
|
|
781
|
+
options: {
|
|
782
|
+
fromAddress: emailConfig.fromAddress,
|
|
783
|
+
mailgunDomain: emailConfig.domain,
|
|
784
|
+
},
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
await this.mailingService.sendAcceptedNotificationEmail(
|
|
788
|
+
notificationData
|
|
789
|
+
);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Use specific admin details
|
|
794
|
+
const adminName = `${admin.contactInfo.firstName} ${admin.contactInfo.lastName}`;
|
|
795
|
+
|
|
796
|
+
const notificationData = {
|
|
797
|
+
invite,
|
|
798
|
+
practitioner: {
|
|
799
|
+
firstName: practitioner.basicInfo.firstName || "",
|
|
800
|
+
lastName: practitioner.basicInfo.lastName || "",
|
|
801
|
+
specialties:
|
|
802
|
+
practitioner.certification?.specialties?.map(
|
|
803
|
+
(s: any) => s.name || s
|
|
804
|
+
) || [],
|
|
805
|
+
profileImageUrl:
|
|
806
|
+
typeof practitioner.basicInfo.profileImageUrl === "string"
|
|
807
|
+
? practitioner.basicInfo.profileImageUrl
|
|
808
|
+
: null,
|
|
809
|
+
experienceYears: undefined, // This would need to be calculated or stored in practitioner data
|
|
810
|
+
},
|
|
811
|
+
clinic: {
|
|
812
|
+
name: clinic.name,
|
|
813
|
+
adminName: adminName,
|
|
814
|
+
adminEmail: admin.contactInfo.email, // Use the specific admin's email
|
|
815
|
+
},
|
|
816
|
+
context: {
|
|
817
|
+
invitationDate: invite.createdAt.toDate().toLocaleDateString(),
|
|
818
|
+
responseDate:
|
|
819
|
+
invite.acceptedAt?.toDate().toLocaleDateString() ||
|
|
820
|
+
new Date().toLocaleDateString(),
|
|
821
|
+
},
|
|
822
|
+
urls: {
|
|
823
|
+
clinicDashboardUrl: emailConfig.clinicDashboardUrl,
|
|
824
|
+
practitionerProfileUrl: emailConfig.practitionerProfileUrl,
|
|
825
|
+
},
|
|
826
|
+
options: {
|
|
827
|
+
fromAddress: emailConfig.fromAddress,
|
|
828
|
+
mailgunDomain: emailConfig.domain,
|
|
829
|
+
},
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
await this.mailingService.sendAcceptedNotificationEmail(notificationData);
|
|
833
|
+
} catch (error) {
|
|
834
|
+
Logger.error(
|
|
835
|
+
`[PractitionerInviteAggService] Error sending acceptance notification email:`,
|
|
836
|
+
error
|
|
837
|
+
);
|
|
838
|
+
throw error;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Sends rejection notification email to clinic admin
|
|
844
|
+
* @param invite The rejected invite
|
|
845
|
+
* @param practitioner The practitioner who rejected
|
|
846
|
+
* @param clinic The clinic that sent the invite
|
|
847
|
+
* @param emailConfig Email configuration
|
|
848
|
+
*/
|
|
849
|
+
private async sendRejectionNotificationEmail(
|
|
850
|
+
invite: PractitionerInvite,
|
|
851
|
+
practitioner: Practitioner,
|
|
852
|
+
clinic: Clinic,
|
|
853
|
+
emailConfig: {
|
|
854
|
+
fromAddress: string;
|
|
855
|
+
domain: string;
|
|
856
|
+
clinicDashboardUrl: string;
|
|
857
|
+
findPractitionersUrl?: string;
|
|
858
|
+
}
|
|
859
|
+
): Promise<void> {
|
|
860
|
+
if (!this.mailingService) return;
|
|
861
|
+
|
|
862
|
+
try {
|
|
863
|
+
// Fetch the admin who created the invite
|
|
864
|
+
const admin = await this.fetchClinicAdminById(invite.invitedBy);
|
|
865
|
+
if (!admin) {
|
|
866
|
+
Logger.warn(
|
|
867
|
+
`[PractitionerInviteAggService] Admin ${invite.invitedBy} not found, using clinic contact email as fallback`
|
|
868
|
+
);
|
|
869
|
+
|
|
870
|
+
// Fallback to clinic contact email
|
|
871
|
+
const notificationData = {
|
|
872
|
+
invite,
|
|
873
|
+
practitioner: {
|
|
874
|
+
firstName: practitioner.basicInfo.firstName || "",
|
|
875
|
+
lastName: practitioner.basicInfo.lastName || "",
|
|
876
|
+
specialties:
|
|
877
|
+
practitioner.certification?.specialties?.map(
|
|
878
|
+
(s: any) => s.name || s
|
|
879
|
+
) || [],
|
|
880
|
+
profileImageUrl:
|
|
881
|
+
typeof practitioner.basicInfo.profileImageUrl === "string"
|
|
882
|
+
? practitioner.basicInfo.profileImageUrl
|
|
883
|
+
: null,
|
|
884
|
+
},
|
|
885
|
+
clinic: {
|
|
886
|
+
name: clinic.name,
|
|
887
|
+
adminName: "Admin",
|
|
888
|
+
adminEmail: clinic.contactInfo.email,
|
|
889
|
+
},
|
|
890
|
+
context: {
|
|
891
|
+
invitationDate: invite.createdAt.toDate().toLocaleDateString(),
|
|
892
|
+
responseDate:
|
|
893
|
+
invite.rejectedAt?.toDate().toLocaleDateString() ||
|
|
894
|
+
new Date().toLocaleDateString(),
|
|
895
|
+
rejectionReason: invite.rejectionReason || undefined,
|
|
896
|
+
},
|
|
897
|
+
urls: {
|
|
898
|
+
clinicDashboardUrl: emailConfig.clinicDashboardUrl,
|
|
899
|
+
findPractitionersUrl: emailConfig.findPractitionersUrl,
|
|
900
|
+
},
|
|
901
|
+
options: {
|
|
902
|
+
fromAddress: emailConfig.fromAddress,
|
|
903
|
+
mailgunDomain: emailConfig.domain,
|
|
904
|
+
},
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
await this.mailingService.sendRejectedNotificationEmail(
|
|
908
|
+
notificationData
|
|
909
|
+
);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Use specific admin details
|
|
914
|
+
const adminName = `${admin.contactInfo.firstName} ${admin.contactInfo.lastName}`;
|
|
915
|
+
|
|
916
|
+
const notificationData = {
|
|
917
|
+
invite,
|
|
918
|
+
practitioner: {
|
|
919
|
+
firstName: practitioner.basicInfo.firstName || "",
|
|
920
|
+
lastName: practitioner.basicInfo.lastName || "",
|
|
921
|
+
specialties:
|
|
922
|
+
practitioner.certification?.specialties?.map(
|
|
923
|
+
(s: any) => s.name || s
|
|
924
|
+
) || [],
|
|
925
|
+
profileImageUrl:
|
|
926
|
+
typeof practitioner.basicInfo.profileImageUrl === "string"
|
|
927
|
+
? practitioner.basicInfo.profileImageUrl
|
|
928
|
+
: null,
|
|
929
|
+
},
|
|
930
|
+
clinic: {
|
|
931
|
+
name: clinic.name,
|
|
932
|
+
adminName: adminName,
|
|
933
|
+
adminEmail: admin.contactInfo.email, // Use the specific admin's email
|
|
934
|
+
},
|
|
935
|
+
context: {
|
|
936
|
+
invitationDate: invite.createdAt.toDate().toLocaleDateString(),
|
|
937
|
+
responseDate:
|
|
938
|
+
invite.rejectedAt?.toDate().toLocaleDateString() ||
|
|
939
|
+
new Date().toLocaleDateString(),
|
|
940
|
+
rejectionReason: invite.rejectionReason || undefined,
|
|
941
|
+
},
|
|
942
|
+
urls: {
|
|
943
|
+
clinicDashboardUrl: emailConfig.clinicDashboardUrl,
|
|
944
|
+
findPractitionersUrl: emailConfig.findPractitionersUrl,
|
|
945
|
+
},
|
|
946
|
+
options: {
|
|
947
|
+
fromAddress: emailConfig.fromAddress,
|
|
948
|
+
mailgunDomain: emailConfig.domain,
|
|
949
|
+
},
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
await this.mailingService.sendRejectedNotificationEmail(notificationData);
|
|
953
|
+
} catch (error) {
|
|
954
|
+
Logger.error(
|
|
955
|
+
`[PractitionerInviteAggService] Error sending rejection notification email:`,
|
|
956
|
+
error
|
|
957
|
+
);
|
|
958
|
+
throw error;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
576
961
|
}
|
package/src/admin/index.ts
CHANGED
|
@@ -33,6 +33,7 @@ import { ReviewsAggregationService } from "./aggregation/reviews/reviews.aggrega
|
|
|
33
33
|
// Import mailing services
|
|
34
34
|
import { BaseMailingService } from "./mailing/base.mailing.service";
|
|
35
35
|
import { PractitionerInviteMailingService } from "./mailing/practitionerInvite/practitionerInvite.mailing";
|
|
36
|
+
import { ExistingPractitionerInviteMailingService } from "./mailing/practitionerInvite/existing-practitioner-invite.mailing";
|
|
36
37
|
|
|
37
38
|
// Import booking services
|
|
38
39
|
import { BookingAdmin } from "./booking/booking.admin";
|
|
@@ -87,7 +88,17 @@ export {
|
|
|
87
88
|
};
|
|
88
89
|
|
|
89
90
|
// Export mailing services
|
|
90
|
-
export {
|
|
91
|
+
export {
|
|
92
|
+
BaseMailingService,
|
|
93
|
+
PractitionerInviteMailingService,
|
|
94
|
+
ExistingPractitionerInviteMailingService,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Export mailing service interfaces
|
|
98
|
+
export type {
|
|
99
|
+
ExistingPractitionerInviteEmailData,
|
|
100
|
+
ClinicAdminNotificationData,
|
|
101
|
+
} from "./mailing/practitionerInvite/existing-practitioner-invite.mailing";
|
|
91
102
|
|
|
92
103
|
// Export booking services
|
|
93
104
|
export { BookingAdmin };
|