@programisto/edrm-exams 0.3.16 → 0.3.17

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.
@@ -1,3 +1,4 @@
1
+ import { Types } from 'mongoose';
1
2
  import { enduranceListener, enduranceEmitter, enduranceEventTypes } from '@programisto/endurance';
2
3
  import Test from '../models/test.model.js';
3
4
  import TestResult from '../models/test-result.model.js';
@@ -53,12 +54,18 @@ async function inviteCandidateToTest(payload) {
53
54
  const testLink = (process.env.TEST_INVITATION_LINK || '') + email;
54
55
  const emailUser = process.env.EMAIL_USER;
55
56
  const emailPassword = process.env.EMAIL_PASSWORD;
57
+ const entityIdForMail = payload.entityId != null
58
+ ? (payload.entityId instanceof Types.ObjectId
59
+ ? payload.entityId
60
+ : new Types.ObjectId(String(payload.entityId)))
61
+ : undefined;
56
62
  await enduranceEmitter.emit(enduranceEventTypes.SEND_EMAIL, {
57
63
  template: 'test-invitation',
58
64
  to: email,
59
65
  from: emailUser,
60
66
  emailUser,
61
67
  emailPassword,
68
+ ...(entityIdForMail && { entityId: entityIdForMail }),
62
69
  data: {
63
70
  firstname: contact.firstname,
64
71
  testName: test?.title || '',
@@ -5,6 +5,7 @@ import TestResult from '../models/test-result.model.js';
5
5
  import Test from '../models/test.model.js';
6
6
  import TestJob from '../models/test-job.model.js';
7
7
  import jwt from 'jsonwebtoken';
8
+ import { Types } from 'mongoose';
8
9
  // Fonction utilitaire pour récupérer le nom du job
9
10
  async function getJobName(targetJob) {
10
11
  // Si c'est déjà une string (ancien format), on la retourne directement
@@ -23,6 +24,29 @@ async function getJobName(targetJob) {
23
24
  }
24
25
  return 'Job inconnu';
25
26
  }
27
+ /** Entité par défaut : candidats sans entityId lui sont rattachés. */
28
+ function isDefaultEntity(req) {
29
+ if (!req?.entity)
30
+ return false;
31
+ const slug = req.entity.slug;
32
+ return req.entity.isDefault === true || slug === 'programisto' || slug === 'progamisto';
33
+ }
34
+ /** Filtre MongoDB pour les candidats de l'entité courante (évite les doublons quand un même contact existe sur plusieurs entités). */
35
+ function buildCandidateEntityFilter(req) {
36
+ if (!req?.entity?._id)
37
+ return {};
38
+ const eid = req.entity._id instanceof Types.ObjectId ? req.entity._id : new Types.ObjectId(String(req.entity._id));
39
+ if (isDefaultEntity(req)) {
40
+ return {
41
+ $or: [
42
+ { entityId: eid },
43
+ { entityId: null },
44
+ { entityId: { $exists: false } }
45
+ ]
46
+ };
47
+ }
48
+ return { entityId: eid };
49
+ }
26
50
  class CandidateRouter extends EnduranceRouter {
27
51
  constructor() {
28
52
  super(EnduranceAuthMiddleware.getInstance());
@@ -204,6 +228,7 @@ class CandidateRouter extends EnduranceRouter {
204
228
  const search = req.query.search || '';
205
229
  const sortBy = req.query.sortBy || 'lastname';
206
230
  const sortOrder = req.query.sortOrder || 'asc';
231
+ const entityFilter = buildCandidateEntityFilter(req);
207
232
  let contactIds = [];
208
233
  let total = 0;
209
234
  if (search) {
@@ -217,27 +242,27 @@ class CandidateRouter extends EnduranceRouter {
217
242
  };
218
243
  const contacts = await ContactModel.find(contactQuery);
219
244
  contactIds = contacts.map(contact => contact._id);
220
- // Compter les candidats avec ces contacts
221
- total = await CandidateModel.countDocuments({ contact: { $in: contactIds } });
245
+ // Compter les candidats avec ces contacts (scopé entité : éviter doublons multi-entités)
246
+ total = await CandidateModel.countDocuments({ contact: { $in: contactIds }, ...entityFilter });
222
247
  }
223
248
  else {
224
- // Pas de recherche, compter tous les candidats
225
- total = await CandidateModel.countDocuments();
249
+ // Pas de recherche, compter tous les candidats (scopé entité)
250
+ total = await CandidateModel.countDocuments(entityFilter);
226
251
  }
227
252
  // Construction du tri pour les contacts
228
253
  const allowedSortFields = ['firstname', 'lastname', 'email'];
229
254
  const sortField = allowedSortFields.includes(sortBy) ? sortBy : 'lastname';
230
255
  let candidates;
231
256
  if (search && contactIds.length > 0) {
232
- // Récupérer les candidats avec les contacts trouvés
233
- candidates = await CandidateModel.find({ contact: { $in: contactIds } })
257
+ // Récupérer les candidats avec les contacts trouvés (un seul par contact/entité)
258
+ candidates = await CandidateModel.find({ contact: { $in: contactIds }, ...entityFilter })
234
259
  .skip(skip)
235
260
  .limit(limit)
236
261
  .exec();
237
262
  }
238
263
  else if (!search) {
239
- // Récupérer tous les candidats
240
- candidates = await CandidateModel.find()
264
+ // Récupérer les candidats de l'entité courante
265
+ candidates = await CandidateModel.find(entityFilter)
241
266
  .skip(skip)
242
267
  .limit(limit)
243
268
  .exec();
@@ -1510,13 +1510,17 @@ class ExamsRouter extends EnduranceRouter {
1510
1510
  // Récupérer les credentials d'envoi
1511
1511
  const emailUser = process.env.EMAIL_USER;
1512
1512
  const emailPassword = process.env.EMAIL_PASSWORD;
1513
- // Envoyer l'email via l'event emitter
1513
+ // Envoyer l'email via l'event emitter (entityId pour utiliser le template de l'entité courante, ex. École de Turing)
1514
+ const entityIdForMail = req.entity?._id != null
1515
+ ? (req.entity._id instanceof Types.ObjectId ? req.entity._id : new Types.ObjectId(String(req.entity._id)))
1516
+ : undefined;
1514
1517
  await emitter.emit(eventTypes.SEND_EMAIL, {
1515
1518
  template: 'test-invitation',
1516
1519
  to: email,
1517
1520
  from: emailUser,
1518
1521
  emailUser,
1519
1522
  emailPassword,
1523
+ ...(entityIdForMail && { entityId: entityIdForMail }),
1520
1524
  data: {
1521
1525
  firstname: contact.firstname,
1522
1526
  testName: test?.title || '',
@@ -2300,13 +2304,17 @@ class ExamsRouter extends EnduranceRouter {
2300
2304
  const emailPassword = process.env.EMAIL_PASSWORD;
2301
2305
  // Construire le lien d'invitation
2302
2306
  const testLink = (process.env.TEST_INVITATION_LINK || '') + email;
2303
- // Envoyer l'email via l'event emitter
2307
+ // Envoyer l'email via l'event emitter (entityId pour utiliser le template de l'entité courante)
2308
+ const entityIdForReinvite = req.entity?._id != null
2309
+ ? (req.entity._id instanceof Types.ObjectId ? req.entity._id : new Types.ObjectId(String(req.entity._id)))
2310
+ : undefined;
2304
2311
  await emitter.emit(eventTypes.SEND_EMAIL, {
2305
2312
  template: 'test-invitation',
2306
2313
  to: email,
2307
2314
  from: emailUser,
2308
2315
  emailUser,
2309
2316
  emailPassword,
2317
+ ...(entityIdForReinvite && { entityId: entityIdForReinvite }),
2310
2318
  data: {
2311
2319
  testLink
2312
2320
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@programisto/edrm-exams",
4
- "version": "0.3.16",
4
+ "version": "0.3.17",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },