@blackcode_sa/metaestetics-api 1.7.34 → 1.7.36

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.
@@ -2282,6 +2282,12 @@ declare class PractitionerInviteAggregationService {
2282
2282
  * @returns {Promise<void>}
2283
2283
  */
2284
2284
  private updatePractitionerWorkingHours;
2285
+ /**
2286
+ * Fetches a clinic admin by ID
2287
+ * @param adminId The clinic admin ID
2288
+ * @returns The clinic admin or null if not found
2289
+ */
2290
+ private fetchClinicAdminById;
2285
2291
  /**
2286
2292
  * Fetches a practitioner by ID.
2287
2293
  * @param practitionerId The practitioner ID.
@@ -2282,6 +2282,12 @@ declare class PractitionerInviteAggregationService {
2282
2282
  * @returns {Promise<void>}
2283
2283
  */
2284
2284
  private updatePractitionerWorkingHours;
2285
+ /**
2286
+ * Fetches a clinic admin by ID
2287
+ * @param adminId The clinic admin ID
2288
+ * @returns The clinic admin or null if not found
2289
+ */
2290
+ private fetchClinicAdminById;
2285
2291
  /**
2286
2292
  * Fetches a practitioner by ID.
2287
2293
  * @param practitionerId The practitioner ID.
@@ -926,6 +926,7 @@ var PractitionerInviteStatus = /* @__PURE__ */ ((PractitionerInviteStatus2) => {
926
926
 
927
927
  // src/types/clinic/index.ts
928
928
  var CLINIC_GROUPS_COLLECTION = "clinic_groups";
929
+ var CLINIC_ADMINS_COLLECTION = "clinic_admins";
929
930
  var CLINICS_COLLECTION = "clinics";
930
931
 
931
932
  // src/types/patient/index.ts
@@ -2236,6 +2237,23 @@ var PractitionerInviteAggregationService = class {
2236
2237
  }
2237
2238
  }
2238
2239
  // --- Data Fetching Helpers ---
2240
+ /**
2241
+ * Fetches a clinic admin by ID
2242
+ * @param adminId The clinic admin ID
2243
+ * @returns The clinic admin or null if not found
2244
+ */
2245
+ async fetchClinicAdminById(adminId) {
2246
+ try {
2247
+ const doc = await this.db.collection(CLINIC_ADMINS_COLLECTION).doc(adminId).get();
2248
+ return doc.exists ? doc.data() : null;
2249
+ } catch (error) {
2250
+ Logger.error(
2251
+ `[PractitionerInviteAggService] Error fetching clinic admin ${adminId}:`,
2252
+ error
2253
+ );
2254
+ return null;
2255
+ }
2256
+ }
2239
2257
  /**
2240
2258
  * Fetches a practitioner by ID.
2241
2259
  * @param practitionerId The practitioner ID.
@@ -2279,40 +2297,88 @@ var PractitionerInviteAggregationService = class {
2279
2297
  * @param emailConfig Email configuration
2280
2298
  */
2281
2299
  async sendAcceptanceNotificationEmail(invite, practitioner, clinic, emailConfig) {
2282
- var _a, _b, _c;
2300
+ var _a, _b, _c, _d, _e, _f;
2283
2301
  if (!this.mailingService) return;
2284
- const notificationData = {
2285
- invite,
2286
- practitioner: {
2287
- firstName: practitioner.basicInfo.firstName || "",
2288
- lastName: practitioner.basicInfo.lastName || "",
2289
- specialties: ((_b = (_a = practitioner.certification) == null ? void 0 : _a.specialties) == null ? void 0 : _b.map(
2290
- (s) => s.name || s
2291
- )) || [],
2292
- profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null,
2293
- experienceYears: void 0
2294
- // This would need to be calculated or stored in practitioner data
2295
- },
2296
- clinic: {
2297
- name: clinic.name,
2298
- adminName: void 0,
2299
- // This would need to be fetched from clinic admin data
2300
- adminEmail: clinic.contactInfo.email
2301
- },
2302
- context: {
2303
- invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2304
- responseDate: ((_c = invite.acceptedAt) == null ? void 0 : _c.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString()
2305
- },
2306
- urls: {
2307
- clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2308
- practitionerProfileUrl: emailConfig.practitionerProfileUrl
2309
- },
2310
- options: {
2311
- fromAddress: emailConfig.fromAddress,
2312
- mailgunDomain: emailConfig.domain
2302
+ try {
2303
+ const admin17 = await this.fetchClinicAdminById(invite.invitedBy);
2304
+ if (!admin17) {
2305
+ Logger.warn(
2306
+ `[PractitionerInviteAggService] Admin ${invite.invitedBy} not found, using clinic contact email as fallback`
2307
+ );
2308
+ const notificationData2 = {
2309
+ invite,
2310
+ practitioner: {
2311
+ firstName: practitioner.basicInfo.firstName || "",
2312
+ lastName: practitioner.basicInfo.lastName || "",
2313
+ specialties: ((_b = (_a = practitioner.certification) == null ? void 0 : _a.specialties) == null ? void 0 : _b.map(
2314
+ (s) => s.name || s
2315
+ )) || [],
2316
+ profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null,
2317
+ experienceYears: void 0
2318
+ },
2319
+ clinic: {
2320
+ name: clinic.name,
2321
+ adminName: "Admin",
2322
+ adminEmail: clinic.contactInfo.email
2323
+ },
2324
+ context: {
2325
+ invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2326
+ responseDate: ((_c = invite.acceptedAt) == null ? void 0 : _c.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString()
2327
+ },
2328
+ urls: {
2329
+ clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2330
+ practitionerProfileUrl: emailConfig.practitionerProfileUrl
2331
+ },
2332
+ options: {
2333
+ fromAddress: emailConfig.fromAddress,
2334
+ mailgunDomain: emailConfig.domain
2335
+ }
2336
+ };
2337
+ await this.mailingService.sendAcceptedNotificationEmail(
2338
+ notificationData2
2339
+ );
2340
+ return;
2313
2341
  }
2314
- };
2315
- await this.mailingService.sendAcceptedNotificationEmail(notificationData);
2342
+ const adminName = `${admin17.contactInfo.firstName} ${admin17.contactInfo.lastName}`;
2343
+ const notificationData = {
2344
+ invite,
2345
+ practitioner: {
2346
+ firstName: practitioner.basicInfo.firstName || "",
2347
+ lastName: practitioner.basicInfo.lastName || "",
2348
+ specialties: ((_e = (_d = practitioner.certification) == null ? void 0 : _d.specialties) == null ? void 0 : _e.map(
2349
+ (s) => s.name || s
2350
+ )) || [],
2351
+ profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null,
2352
+ experienceYears: void 0
2353
+ // This would need to be calculated or stored in practitioner data
2354
+ },
2355
+ clinic: {
2356
+ name: clinic.name,
2357
+ adminName,
2358
+ adminEmail: admin17.contactInfo.email
2359
+ // Use the specific admin's email
2360
+ },
2361
+ context: {
2362
+ invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2363
+ responseDate: ((_f = invite.acceptedAt) == null ? void 0 : _f.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString()
2364
+ },
2365
+ urls: {
2366
+ clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2367
+ practitionerProfileUrl: emailConfig.practitionerProfileUrl
2368
+ },
2369
+ options: {
2370
+ fromAddress: emailConfig.fromAddress,
2371
+ mailgunDomain: emailConfig.domain
2372
+ }
2373
+ };
2374
+ await this.mailingService.sendAcceptedNotificationEmail(notificationData);
2375
+ } catch (error) {
2376
+ Logger.error(
2377
+ `[PractitionerInviteAggService] Error sending acceptance notification email:`,
2378
+ error
2379
+ );
2380
+ throw error;
2381
+ }
2316
2382
  }
2317
2383
  /**
2318
2384
  * Sends rejection notification email to clinic admin
@@ -2322,39 +2388,87 @@ var PractitionerInviteAggregationService = class {
2322
2388
  * @param emailConfig Email configuration
2323
2389
  */
2324
2390
  async sendRejectionNotificationEmail(invite, practitioner, clinic, emailConfig) {
2325
- var _a, _b, _c;
2391
+ var _a, _b, _c, _d, _e, _f;
2326
2392
  if (!this.mailingService) return;
2327
- const notificationData = {
2328
- invite,
2329
- practitioner: {
2330
- firstName: practitioner.basicInfo.firstName || "",
2331
- lastName: practitioner.basicInfo.lastName || "",
2332
- specialties: ((_b = (_a = practitioner.certification) == null ? void 0 : _a.specialties) == null ? void 0 : _b.map(
2333
- (s) => s.name || s
2334
- )) || [],
2335
- profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null
2336
- },
2337
- clinic: {
2338
- name: clinic.name,
2339
- adminName: void 0,
2340
- // This would need to be fetched from clinic admin data
2341
- adminEmail: clinic.contactInfo.email
2342
- },
2343
- context: {
2344
- invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2345
- responseDate: ((_c = invite.rejectedAt) == null ? void 0 : _c.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString(),
2346
- rejectionReason: invite.rejectionReason || void 0
2347
- },
2348
- urls: {
2349
- clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2350
- findPractitionersUrl: emailConfig.findPractitionersUrl
2351
- },
2352
- options: {
2353
- fromAddress: emailConfig.fromAddress,
2354
- mailgunDomain: emailConfig.domain
2393
+ try {
2394
+ const admin17 = await this.fetchClinicAdminById(invite.invitedBy);
2395
+ if (!admin17) {
2396
+ Logger.warn(
2397
+ `[PractitionerInviteAggService] Admin ${invite.invitedBy} not found, using clinic contact email as fallback`
2398
+ );
2399
+ const notificationData2 = {
2400
+ invite,
2401
+ practitioner: {
2402
+ firstName: practitioner.basicInfo.firstName || "",
2403
+ lastName: practitioner.basicInfo.lastName || "",
2404
+ specialties: ((_b = (_a = practitioner.certification) == null ? void 0 : _a.specialties) == null ? void 0 : _b.map(
2405
+ (s) => s.name || s
2406
+ )) || [],
2407
+ profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null
2408
+ },
2409
+ clinic: {
2410
+ name: clinic.name,
2411
+ adminName: "Admin",
2412
+ adminEmail: clinic.contactInfo.email
2413
+ },
2414
+ context: {
2415
+ invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2416
+ responseDate: ((_c = invite.rejectedAt) == null ? void 0 : _c.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString(),
2417
+ rejectionReason: invite.rejectionReason || void 0
2418
+ },
2419
+ urls: {
2420
+ clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2421
+ findPractitionersUrl: emailConfig.findPractitionersUrl
2422
+ },
2423
+ options: {
2424
+ fromAddress: emailConfig.fromAddress,
2425
+ mailgunDomain: emailConfig.domain
2426
+ }
2427
+ };
2428
+ await this.mailingService.sendRejectedNotificationEmail(
2429
+ notificationData2
2430
+ );
2431
+ return;
2355
2432
  }
2356
- };
2357
- await this.mailingService.sendRejectedNotificationEmail(notificationData);
2433
+ const adminName = `${admin17.contactInfo.firstName} ${admin17.contactInfo.lastName}`;
2434
+ const notificationData = {
2435
+ invite,
2436
+ practitioner: {
2437
+ firstName: practitioner.basicInfo.firstName || "",
2438
+ lastName: practitioner.basicInfo.lastName || "",
2439
+ specialties: ((_e = (_d = practitioner.certification) == null ? void 0 : _d.specialties) == null ? void 0 : _e.map(
2440
+ (s) => s.name || s
2441
+ )) || [],
2442
+ profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null
2443
+ },
2444
+ clinic: {
2445
+ name: clinic.name,
2446
+ adminName,
2447
+ adminEmail: admin17.contactInfo.email
2448
+ // Use the specific admin's email
2449
+ },
2450
+ context: {
2451
+ invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2452
+ responseDate: ((_f = invite.rejectedAt) == null ? void 0 : _f.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString(),
2453
+ rejectionReason: invite.rejectionReason || void 0
2454
+ },
2455
+ urls: {
2456
+ clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2457
+ findPractitionersUrl: emailConfig.findPractitionersUrl
2458
+ },
2459
+ options: {
2460
+ fromAddress: emailConfig.fromAddress,
2461
+ mailgunDomain: emailConfig.domain
2462
+ }
2463
+ };
2464
+ await this.mailingService.sendRejectedNotificationEmail(notificationData);
2465
+ } catch (error) {
2466
+ Logger.error(
2467
+ `[PractitionerInviteAggService] Error sending rejection notification email:`,
2468
+ error
2469
+ );
2470
+ throw error;
2471
+ }
2358
2472
  }
2359
2473
  };
2360
2474
 
@@ -865,6 +865,7 @@ var PractitionerInviteStatus = /* @__PURE__ */ ((PractitionerInviteStatus2) => {
865
865
 
866
866
  // src/types/clinic/index.ts
867
867
  var CLINIC_GROUPS_COLLECTION = "clinic_groups";
868
+ var CLINIC_ADMINS_COLLECTION = "clinic_admins";
868
869
  var CLINICS_COLLECTION = "clinics";
869
870
 
870
871
  // src/types/patient/index.ts
@@ -2175,6 +2176,23 @@ var PractitionerInviteAggregationService = class {
2175
2176
  }
2176
2177
  }
2177
2178
  // --- Data Fetching Helpers ---
2179
+ /**
2180
+ * Fetches a clinic admin by ID
2181
+ * @param adminId The clinic admin ID
2182
+ * @returns The clinic admin or null if not found
2183
+ */
2184
+ async fetchClinicAdminById(adminId) {
2185
+ try {
2186
+ const doc = await this.db.collection(CLINIC_ADMINS_COLLECTION).doc(adminId).get();
2187
+ return doc.exists ? doc.data() : null;
2188
+ } catch (error) {
2189
+ Logger.error(
2190
+ `[PractitionerInviteAggService] Error fetching clinic admin ${adminId}:`,
2191
+ error
2192
+ );
2193
+ return null;
2194
+ }
2195
+ }
2178
2196
  /**
2179
2197
  * Fetches a practitioner by ID.
2180
2198
  * @param practitionerId The practitioner ID.
@@ -2218,40 +2236,88 @@ var PractitionerInviteAggregationService = class {
2218
2236
  * @param emailConfig Email configuration
2219
2237
  */
2220
2238
  async sendAcceptanceNotificationEmail(invite, practitioner, clinic, emailConfig) {
2221
- var _a, _b, _c;
2239
+ var _a, _b, _c, _d, _e, _f;
2222
2240
  if (!this.mailingService) return;
2223
- const notificationData = {
2224
- invite,
2225
- practitioner: {
2226
- firstName: practitioner.basicInfo.firstName || "",
2227
- lastName: practitioner.basicInfo.lastName || "",
2228
- specialties: ((_b = (_a = practitioner.certification) == null ? void 0 : _a.specialties) == null ? void 0 : _b.map(
2229
- (s) => s.name || s
2230
- )) || [],
2231
- profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null,
2232
- experienceYears: void 0
2233
- // This would need to be calculated or stored in practitioner data
2234
- },
2235
- clinic: {
2236
- name: clinic.name,
2237
- adminName: void 0,
2238
- // This would need to be fetched from clinic admin data
2239
- adminEmail: clinic.contactInfo.email
2240
- },
2241
- context: {
2242
- invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2243
- responseDate: ((_c = invite.acceptedAt) == null ? void 0 : _c.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString()
2244
- },
2245
- urls: {
2246
- clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2247
- practitionerProfileUrl: emailConfig.practitionerProfileUrl
2248
- },
2249
- options: {
2250
- fromAddress: emailConfig.fromAddress,
2251
- mailgunDomain: emailConfig.domain
2241
+ try {
2242
+ const admin17 = await this.fetchClinicAdminById(invite.invitedBy);
2243
+ if (!admin17) {
2244
+ Logger.warn(
2245
+ `[PractitionerInviteAggService] Admin ${invite.invitedBy} not found, using clinic contact email as fallback`
2246
+ );
2247
+ const notificationData2 = {
2248
+ invite,
2249
+ practitioner: {
2250
+ firstName: practitioner.basicInfo.firstName || "",
2251
+ lastName: practitioner.basicInfo.lastName || "",
2252
+ specialties: ((_b = (_a = practitioner.certification) == null ? void 0 : _a.specialties) == null ? void 0 : _b.map(
2253
+ (s) => s.name || s
2254
+ )) || [],
2255
+ profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null,
2256
+ experienceYears: void 0
2257
+ },
2258
+ clinic: {
2259
+ name: clinic.name,
2260
+ adminName: "Admin",
2261
+ adminEmail: clinic.contactInfo.email
2262
+ },
2263
+ context: {
2264
+ invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2265
+ responseDate: ((_c = invite.acceptedAt) == null ? void 0 : _c.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString()
2266
+ },
2267
+ urls: {
2268
+ clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2269
+ practitionerProfileUrl: emailConfig.practitionerProfileUrl
2270
+ },
2271
+ options: {
2272
+ fromAddress: emailConfig.fromAddress,
2273
+ mailgunDomain: emailConfig.domain
2274
+ }
2275
+ };
2276
+ await this.mailingService.sendAcceptedNotificationEmail(
2277
+ notificationData2
2278
+ );
2279
+ return;
2252
2280
  }
2253
- };
2254
- await this.mailingService.sendAcceptedNotificationEmail(notificationData);
2281
+ const adminName = `${admin17.contactInfo.firstName} ${admin17.contactInfo.lastName}`;
2282
+ const notificationData = {
2283
+ invite,
2284
+ practitioner: {
2285
+ firstName: practitioner.basicInfo.firstName || "",
2286
+ lastName: practitioner.basicInfo.lastName || "",
2287
+ specialties: ((_e = (_d = practitioner.certification) == null ? void 0 : _d.specialties) == null ? void 0 : _e.map(
2288
+ (s) => s.name || s
2289
+ )) || [],
2290
+ profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null,
2291
+ experienceYears: void 0
2292
+ // This would need to be calculated or stored in practitioner data
2293
+ },
2294
+ clinic: {
2295
+ name: clinic.name,
2296
+ adminName,
2297
+ adminEmail: admin17.contactInfo.email
2298
+ // Use the specific admin's email
2299
+ },
2300
+ context: {
2301
+ invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2302
+ responseDate: ((_f = invite.acceptedAt) == null ? void 0 : _f.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString()
2303
+ },
2304
+ urls: {
2305
+ clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2306
+ practitionerProfileUrl: emailConfig.practitionerProfileUrl
2307
+ },
2308
+ options: {
2309
+ fromAddress: emailConfig.fromAddress,
2310
+ mailgunDomain: emailConfig.domain
2311
+ }
2312
+ };
2313
+ await this.mailingService.sendAcceptedNotificationEmail(notificationData);
2314
+ } catch (error) {
2315
+ Logger.error(
2316
+ `[PractitionerInviteAggService] Error sending acceptance notification email:`,
2317
+ error
2318
+ );
2319
+ throw error;
2320
+ }
2255
2321
  }
2256
2322
  /**
2257
2323
  * Sends rejection notification email to clinic admin
@@ -2261,39 +2327,87 @@ var PractitionerInviteAggregationService = class {
2261
2327
  * @param emailConfig Email configuration
2262
2328
  */
2263
2329
  async sendRejectionNotificationEmail(invite, practitioner, clinic, emailConfig) {
2264
- var _a, _b, _c;
2330
+ var _a, _b, _c, _d, _e, _f;
2265
2331
  if (!this.mailingService) return;
2266
- const notificationData = {
2267
- invite,
2268
- practitioner: {
2269
- firstName: practitioner.basicInfo.firstName || "",
2270
- lastName: practitioner.basicInfo.lastName || "",
2271
- specialties: ((_b = (_a = practitioner.certification) == null ? void 0 : _a.specialties) == null ? void 0 : _b.map(
2272
- (s) => s.name || s
2273
- )) || [],
2274
- profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null
2275
- },
2276
- clinic: {
2277
- name: clinic.name,
2278
- adminName: void 0,
2279
- // This would need to be fetched from clinic admin data
2280
- adminEmail: clinic.contactInfo.email
2281
- },
2282
- context: {
2283
- invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2284
- responseDate: ((_c = invite.rejectedAt) == null ? void 0 : _c.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString(),
2285
- rejectionReason: invite.rejectionReason || void 0
2286
- },
2287
- urls: {
2288
- clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2289
- findPractitionersUrl: emailConfig.findPractitionersUrl
2290
- },
2291
- options: {
2292
- fromAddress: emailConfig.fromAddress,
2293
- mailgunDomain: emailConfig.domain
2332
+ try {
2333
+ const admin17 = await this.fetchClinicAdminById(invite.invitedBy);
2334
+ if (!admin17) {
2335
+ Logger.warn(
2336
+ `[PractitionerInviteAggService] Admin ${invite.invitedBy} not found, using clinic contact email as fallback`
2337
+ );
2338
+ const notificationData2 = {
2339
+ invite,
2340
+ practitioner: {
2341
+ firstName: practitioner.basicInfo.firstName || "",
2342
+ lastName: practitioner.basicInfo.lastName || "",
2343
+ specialties: ((_b = (_a = practitioner.certification) == null ? void 0 : _a.specialties) == null ? void 0 : _b.map(
2344
+ (s) => s.name || s
2345
+ )) || [],
2346
+ profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null
2347
+ },
2348
+ clinic: {
2349
+ name: clinic.name,
2350
+ adminName: "Admin",
2351
+ adminEmail: clinic.contactInfo.email
2352
+ },
2353
+ context: {
2354
+ invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2355
+ responseDate: ((_c = invite.rejectedAt) == null ? void 0 : _c.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString(),
2356
+ rejectionReason: invite.rejectionReason || void 0
2357
+ },
2358
+ urls: {
2359
+ clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2360
+ findPractitionersUrl: emailConfig.findPractitionersUrl
2361
+ },
2362
+ options: {
2363
+ fromAddress: emailConfig.fromAddress,
2364
+ mailgunDomain: emailConfig.domain
2365
+ }
2366
+ };
2367
+ await this.mailingService.sendRejectedNotificationEmail(
2368
+ notificationData2
2369
+ );
2370
+ return;
2294
2371
  }
2295
- };
2296
- await this.mailingService.sendRejectedNotificationEmail(notificationData);
2372
+ const adminName = `${admin17.contactInfo.firstName} ${admin17.contactInfo.lastName}`;
2373
+ const notificationData = {
2374
+ invite,
2375
+ practitioner: {
2376
+ firstName: practitioner.basicInfo.firstName || "",
2377
+ lastName: practitioner.basicInfo.lastName || "",
2378
+ specialties: ((_e = (_d = practitioner.certification) == null ? void 0 : _d.specialties) == null ? void 0 : _e.map(
2379
+ (s) => s.name || s
2380
+ )) || [],
2381
+ profileImageUrl: typeof practitioner.basicInfo.profileImageUrl === "string" ? practitioner.basicInfo.profileImageUrl : null
2382
+ },
2383
+ clinic: {
2384
+ name: clinic.name,
2385
+ adminName,
2386
+ adminEmail: admin17.contactInfo.email
2387
+ // Use the specific admin's email
2388
+ },
2389
+ context: {
2390
+ invitationDate: invite.createdAt.toDate().toLocaleDateString(),
2391
+ responseDate: ((_f = invite.rejectedAt) == null ? void 0 : _f.toDate().toLocaleDateString()) || (/* @__PURE__ */ new Date()).toLocaleDateString(),
2392
+ rejectionReason: invite.rejectionReason || void 0
2393
+ },
2394
+ urls: {
2395
+ clinicDashboardUrl: emailConfig.clinicDashboardUrl,
2396
+ findPractitionersUrl: emailConfig.findPractitionersUrl
2397
+ },
2398
+ options: {
2399
+ fromAddress: emailConfig.fromAddress,
2400
+ mailgunDomain: emailConfig.domain
2401
+ }
2402
+ };
2403
+ await this.mailingService.sendRejectedNotificationEmail(notificationData);
2404
+ } catch (error) {
2405
+ Logger.error(
2406
+ `[PractitionerInviteAggService] Error sending rejection notification email:`,
2407
+ error
2408
+ );
2409
+ throw error;
2410
+ }
2297
2411
  }
2298
2412
  };
2299
2413
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blackcode_sa/metaestetics-api",
3
3
  "private": false,
4
- "version": "1.7.34",
4
+ "version": "1.7.36",
5
5
  "description": "Firebase authentication service with anonymous upgrade support",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -10,7 +10,12 @@ import {
10
10
  PRACTITIONERS_COLLECTION,
11
11
  PractitionerClinicWorkingHours,
12
12
  } from "../../../types/practitioner";
13
- import { CLINICS_COLLECTION, Clinic } from "../../../types/clinic";
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";
16
21
  import { ExistingPractitionerInviteMailingService } from "../../mailing/practitionerInvite/existing-practitioner-invite.mailing";
@@ -645,6 +650,29 @@ export class PractitionerInviteAggregationService {
645
650
 
646
651
  // --- Data Fetching Helpers ---
647
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
+
648
676
  /**
649
677
  * Fetches a practitioner by ID.
650
678
  * @param practitionerId The practitioner ID.
@@ -711,43 +739,104 @@ export class PractitionerInviteAggregationService {
711
739
  ): Promise<void> {
712
740
  if (!this.mailingService) return;
713
741
 
714
- const notificationData = {
715
- invite,
716
- practitioner: {
717
- firstName: practitioner.basicInfo.firstName || "",
718
- lastName: practitioner.basicInfo.lastName || "",
719
- specialties:
720
- practitioner.certification?.specialties?.map(
721
- (s: any) => s.name || s
722
- ) || [],
723
- profileImageUrl:
724
- typeof practitioner.basicInfo.profileImageUrl === "string"
725
- ? practitioner.basicInfo.profileImageUrl
726
- : null,
727
- experienceYears: undefined, // This would need to be calculated or stored in practitioner data
728
- },
729
- clinic: {
730
- name: clinic.name,
731
- adminName: undefined, // This would need to be fetched from clinic admin data
732
- adminEmail: clinic.contactInfo.email,
733
- },
734
- context: {
735
- invitationDate: invite.createdAt.toDate().toLocaleDateString(),
736
- responseDate:
737
- invite.acceptedAt?.toDate().toLocaleDateString() ||
738
- new Date().toLocaleDateString(),
739
- },
740
- urls: {
741
- clinicDashboardUrl: emailConfig.clinicDashboardUrl,
742
- practitionerProfileUrl: emailConfig.practitionerProfileUrl,
743
- },
744
- options: {
745
- fromAddress: emailConfig.fromAddress,
746
- mailgunDomain: emailConfig.domain,
747
- },
748
- };
749
-
750
- await this.mailingService.sendAcceptedNotificationEmail(notificationData);
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
+ }
751
840
  }
752
841
 
753
842
  /**
@@ -770,42 +859,103 @@ export class PractitionerInviteAggregationService {
770
859
  ): Promise<void> {
771
860
  if (!this.mailingService) return;
772
861
 
773
- const notificationData = {
774
- invite,
775
- practitioner: {
776
- firstName: practitioner.basicInfo.firstName || "",
777
- lastName: practitioner.basicInfo.lastName || "",
778
- specialties:
779
- practitioner.certification?.specialties?.map(
780
- (s: any) => s.name || s
781
- ) || [],
782
- profileImageUrl:
783
- typeof practitioner.basicInfo.profileImageUrl === "string"
784
- ? practitioner.basicInfo.profileImageUrl
785
- : null,
786
- },
787
- clinic: {
788
- name: clinic.name,
789
- adminName: undefined, // This would need to be fetched from clinic admin data
790
- adminEmail: clinic.contactInfo.email,
791
- },
792
- context: {
793
- invitationDate: invite.createdAt.toDate().toLocaleDateString(),
794
- responseDate:
795
- invite.rejectedAt?.toDate().toLocaleDateString() ||
796
- new Date().toLocaleDateString(),
797
- rejectionReason: invite.rejectionReason || undefined,
798
- },
799
- urls: {
800
- clinicDashboardUrl: emailConfig.clinicDashboardUrl,
801
- findPractitionersUrl: emailConfig.findPractitionersUrl,
802
- },
803
- options: {
804
- fromAddress: emailConfig.fromAddress,
805
- mailgunDomain: emailConfig.domain,
806
- },
807
- };
808
-
809
- await this.mailingService.sendRejectedNotificationEmail(notificationData);
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
+ }
810
960
  }
811
961
  }