@programisto/edrm-exams 0.2.13 → 0.3.0

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.
@@ -48,16 +48,49 @@ async function correctTest(options) {
48
48
  }, true);
49
49
  console.log('Correction result:', { scoreResponse });
50
50
  const parsedResult = JSON.parse(scoreResponse);
51
- finalscore += parsedResult.score;
52
- dbResponse.score = parsedResult.score;
51
+ // Valider le score retourné par l'IA
52
+ let validScore = 0;
53
+ if (parsedResult.score !== undefined && parsedResult.score !== null) {
54
+ const score = parseFloat(parsedResult.score.toString());
55
+ if (!isNaN(score) && isFinite(score) && score >= 0) {
56
+ validScore = score;
57
+ }
58
+ else {
59
+ console.warn('Invalid score returned by AI:', parsedResult.score);
60
+ }
61
+ }
62
+ finalscore += validScore;
63
+ dbResponse.score = validScore;
53
64
  dbResponse.comment = parsedResult.comment || '';
54
65
  }
66
+ // S'assurer que finalscore est un nombre valide
67
+ if (isNaN(finalscore) || !isFinite(finalscore)) {
68
+ console.warn('Invalid finalscore calculated, setting to 0:', finalscore);
69
+ finalscore = 0;
70
+ }
71
+ // S'assurer que maxScore est un nombre valide
72
+ if (isNaN(maxScore) || !isFinite(maxScore)) {
73
+ console.warn('Invalid maxScore calculated, setting to 0:', maxScore);
74
+ maxScore = 0;
75
+ }
55
76
  // Mettre à jour le score final et l'état
56
77
  result.score = finalscore;
57
78
  result.state = TestState.Finish;
58
79
  // Forcer la sauvegarde des sous-documents responses
59
80
  result.markModified('responses');
60
- const scorePercentage = Math.ceil((finalscore / maxScore) * 100);
81
+ // Calculer le pourcentage de score en évitant la division par zéro
82
+ let scorePercentage = 0;
83
+ if (maxScore > 0) {
84
+ scorePercentage = Math.ceil((finalscore / maxScore) * 100);
85
+ }
86
+ else if (finalscore > 0) {
87
+ // Si maxScore est 0 mais qu'il y a un score, on met 100%
88
+ scorePercentage = 100;
89
+ }
90
+ // S'assurer que le score est un nombre valide
91
+ if (isNaN(scorePercentage) || !isFinite(scorePercentage)) {
92
+ scorePercentage = 0;
93
+ }
61
94
  // Sauvegarder les modifications avec findByIdAndUpdate pour éviter les conflits de version
62
95
  await TestResult.findByIdAndUpdate(result._id, {
63
96
  $set: {
@@ -0,0 +1,7 @@
1
+ import { EnduranceSchema } from '@programisto/endurance-core';
2
+ declare class TestJob extends EnduranceSchema {
3
+ name: string;
4
+ static getModel(): import("@typegoose/typegoose").ReturnModelType<typeof TestJob, import("@typegoose/typegoose/lib/types").BeAnObject>;
5
+ }
6
+ declare const TestJobModel: import("@typegoose/typegoose").ReturnModelType<typeof TestJob, import("@typegoose/typegoose/lib/types").BeAnObject>;
7
+ export default TestJobModel;
@@ -0,0 +1,29 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { EnduranceSchema, EnduranceModelType } from '@programisto/endurance-core';
11
+ let TestJob = class TestJob extends EnduranceSchema {
12
+ name;
13
+ static getModel() {
14
+ return TestJobModel;
15
+ }
16
+ };
17
+ __decorate([
18
+ EnduranceModelType.prop({ required: true }),
19
+ __metadata("design:type", String)
20
+ ], TestJob.prototype, "name", void 0);
21
+ TestJob = __decorate([
22
+ EnduranceModelType.modelOptions({
23
+ options: {
24
+ allowMixed: EnduranceModelType.Severity.ALLOW
25
+ }
26
+ })
27
+ ], TestJob);
28
+ const TestJobModel = EnduranceModelType.getModelForClass(TestJob);
29
+ export default TestJobModel;
@@ -2,19 +2,13 @@ import { EnduranceSchema } from '@programisto/endurance-core';
2
2
  import Company from './company.model.js';
3
3
  import TestQuestion from './test-question.model.js';
4
4
  import TestCategory from './test-category.models.js';
5
+ import TestJob from './test-job.model.js';
5
6
  import User from './user.model.js';
6
7
  declare enum TestState {
7
8
  Draft = "draft",
8
9
  Published = "published",
9
10
  Archived = "archived"
10
11
  }
11
- declare enum JobType {
12
- FrontEnd = "front-end",
13
- BackEnd = "back-end",
14
- Fullstack = "fullstack",
15
- DevOps = "devops",
16
- Data = "data"
17
- }
18
12
  declare enum SeniorityLevel {
19
13
  Student = "student",
20
14
  Junior = "junior",
@@ -44,9 +38,10 @@ declare class Test extends EnduranceSchema {
44
38
  duration?: number;
45
39
  passingScore?: number;
46
40
  categories?: TestCategoryWithExpertise[];
47
- targetJob: JobType;
41
+ targetJob: typeof TestJob;
48
42
  seniorityLevel: SeniorityLevel;
49
43
  static getModel(): import("@typegoose/typegoose").ReturnModelType<typeof Test, import("@typegoose/typegoose/lib/types.js").BeAnObject>;
44
+ migrateTargetJob(): Promise<void>;
50
45
  }
51
46
  declare const TestModel: import("@typegoose/typegoose").ReturnModelType<typeof Test, import("@typegoose/typegoose/lib/types.js").BeAnObject>;
52
47
  export default TestModel;
@@ -9,6 +9,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  };
10
10
  import { EnduranceSchema, EnduranceModelType } from '@programisto/endurance-core';
11
11
  import Company from './company.model.js';
12
+ import TestJob from './test-job.model.js';
12
13
  import User from './user.model.js';
13
14
  var TestState;
14
15
  (function (TestState) {
@@ -19,19 +20,6 @@ var TestState;
19
20
  // eslint-disable-next-line no-unused-vars
20
21
  TestState["Archived"] = "archived";
21
22
  })(TestState || (TestState = {}));
22
- var JobType;
23
- (function (JobType) {
24
- // eslint-disable-next-line no-unused-vars
25
- JobType["FrontEnd"] = "front-end";
26
- // eslint-disable-next-line no-unused-vars
27
- JobType["BackEnd"] = "back-end";
28
- // eslint-disable-next-line no-unused-vars
29
- JobType["Fullstack"] = "fullstack";
30
- // eslint-disable-next-line no-unused-vars
31
- JobType["DevOps"] = "devops";
32
- // eslint-disable-next-line no-unused-vars
33
- JobType["Data"] = "data";
34
- })(JobType || (JobType = {}));
35
23
  var SeniorityLevel;
36
24
  (function (SeniorityLevel) {
37
25
  // eslint-disable-next-line no-unused-vars
@@ -67,6 +55,28 @@ let Test = class Test extends EnduranceSchema {
67
55
  static getModel() {
68
56
  return TestModel;
69
57
  }
58
+ // Méthode pour migrer automatiquement les anciennes données
59
+ async migrateTargetJob() {
60
+ const testData = this;
61
+ // Si targetJob est une string (ancien format), on la migre
62
+ if (typeof testData.targetJob === 'string') {
63
+ try {
64
+ // Chercher si le job existe déjà
65
+ let jobType = await TestJob.findOne({ name: testData.targetJob });
66
+ // Si pas trouvé, on le crée
67
+ if (!jobType) {
68
+ jobType = new TestJob({ name: testData.targetJob });
69
+ await jobType.save();
70
+ }
71
+ // Mettre à jour la référence
72
+ this.targetJob = jobType._id;
73
+ await this.save();
74
+ }
75
+ catch (error) {
76
+ console.error('Erreur lors de la migration du targetJob:', error);
77
+ }
78
+ }
79
+ }
70
80
  };
71
81
  __decorate([
72
82
  EnduranceModelType.prop({ required: true }),
@@ -105,8 +115,8 @@ __decorate([
105
115
  __metadata("design:type", Array)
106
116
  ], Test.prototype, "categories", void 0);
107
117
  __decorate([
108
- EnduranceModelType.prop({ required: true, enum: JobType }),
109
- __metadata("design:type", String)
118
+ EnduranceModelType.prop({ ref: () => TestJob, required: true }),
119
+ __metadata("design:type", Object)
110
120
  ], Test.prototype, "targetJob", void 0);
111
121
  __decorate([
112
122
  EnduranceModelType.prop({ required: true, enum: SeniorityLevel }),
@@ -3,7 +3,26 @@ import CandidateModel from '../models/candidate.model.js';
3
3
  import ContactModel from '../models/contact.model.js';
4
4
  import TestResult from '../models/test-result.model.js';
5
5
  import Test from '../models/test.model.js';
6
+ import TestJob from '../models/test-job.model.js';
6
7
  import jwt from 'jsonwebtoken';
8
+ // Fonction utilitaire pour récupérer le nom du job
9
+ async function getJobName(targetJob) {
10
+ // Si c'est déjà une string (ancien format), on la retourne directement
11
+ if (typeof targetJob === 'string') {
12
+ return targetJob;
13
+ }
14
+ // Si c'est un ObjectId, on récupère le job
15
+ if (targetJob && typeof targetJob === 'object' && targetJob._id) {
16
+ const job = await TestJob.findById(targetJob._id);
17
+ return job ? job.name : 'Job inconnu';
18
+ }
19
+ // Si c'est juste un ObjectId
20
+ if (targetJob && typeof targetJob === 'object' && targetJob.toString) {
21
+ const job = await TestJob.findById(targetJob);
22
+ return job ? job.name : 'Job inconnu';
23
+ }
24
+ return 'Job inconnu';
25
+ }
7
26
  class CandidateRouter extends EnduranceRouter {
8
27
  constructor() {
9
28
  super(EnduranceAuthMiddleware.getInstance());
@@ -383,7 +402,7 @@ class CandidateRouter extends EnduranceRouter {
383
402
  const categoriesDocs = await TestCategory.find({ _id: { $in: allCategoryIds } }).lean();
384
403
  const categoriesMap = new Map(categoriesDocs.map(cat => [cat._id.toString(), cat.name]));
385
404
  // Combiner les résultats avec les informations des tests et des catégories
386
- const resultsWithTests = results.map(result => {
405
+ const resultsWithTests = await Promise.all(results.map(async (result) => {
387
406
  const test = testsMap.get(result.testId.toString());
388
407
  let categoriesWithNames = [];
389
408
  if (test && test.categories) {
@@ -398,13 +417,13 @@ class CandidateRouter extends EnduranceRouter {
398
417
  ? {
399
418
  title: test.title,
400
419
  description: test.description,
401
- targetJob: test.targetJob,
420
+ targetJob: await getJobName(test.targetJob),
402
421
  seniorityLevel: test.seniorityLevel,
403
422
  categories: categoriesWithNames
404
423
  }
405
424
  : null
406
425
  };
407
- });
426
+ }));
408
427
  const totalPages = Math.ceil(total / limit);
409
428
  return res.json({
410
429
  data: resultsWithTests,
@@ -3,9 +3,34 @@ import Test from '../models/test.model.js';
3
3
  import TestQuestion from '../models/test-question.model.js';
4
4
  import TestResult from '../models/test-result.model.js';
5
5
  import TestCategory from '../models/test-category.models.js';
6
+ import TestJob from '../models/test-job.model.js';
6
7
  import Candidate from '../models/candidate.model.js';
7
8
  import ContactModel from '../models/contact.model.js';
8
9
  import { generateLiveMessage, generateLiveMessageAssistant } from '../lib/openai.js';
10
+ // Fonction utilitaire pour récupérer le nom du job
11
+ async function getJobName(targetJob) {
12
+ // Si c'est déjà une string (ancien format), on la retourne directement
13
+ if (typeof targetJob === 'string') {
14
+ return targetJob;
15
+ }
16
+ // Si c'est un ObjectId, on récupère le job
17
+ if (targetJob && typeof targetJob === 'object' && targetJob._id) {
18
+ const job = await TestJob.findById(targetJob._id);
19
+ return job ? job.name : 'Job inconnu';
20
+ }
21
+ // Si c'est juste un ObjectId
22
+ if (targetJob && typeof targetJob === 'object' && targetJob.toString) {
23
+ const job = await TestJob.findById(targetJob);
24
+ return job ? job.name : 'Job inconnu';
25
+ }
26
+ return 'Job inconnu';
27
+ }
28
+ // Fonction pour migrer automatiquement un test si nécessaire
29
+ async function migrateTestIfNeeded(test) {
30
+ if (typeof test.targetJob === 'string') {
31
+ await test.migrateTargetJob();
32
+ }
33
+ }
9
34
  class ExamsRouter extends EnduranceRouter {
10
35
  constructor() {
11
36
  super(EnduranceAuthMiddleware.getInstance());
@@ -20,8 +45,9 @@ class ExamsRouter extends EnduranceRouter {
20
45
  // Récupérer les questions existantes pour éviter les doublons
21
46
  const otherQuestionsIds = test.questions.map(question => question.questionId);
22
47
  const otherQuestions = await TestQuestion.find({ _id: { $in: otherQuestionsIds } });
48
+ const jobName = await getJobName(test.targetJob);
23
49
  const questionParams = {
24
- job: test.targetJob,
50
+ job: jobName,
25
51
  seniority: test.seniorityLevel,
26
52
  category: categoryDoc.name,
27
53
  questionType: ['MCQ', 'free question', 'exercice'][Math.floor(Math.random() * 3)],
@@ -98,6 +124,78 @@ class ExamsRouter extends EnduranceRouter {
98
124
  res.status(500).json({ message: 'Internal server error' });
99
125
  }
100
126
  });
127
+ // Créer un job type
128
+ this.post('/jobs', authenticatedOptions, async (req, res) => {
129
+ const { name } = req.body;
130
+ if (!name) {
131
+ return res.status(400).json({ message: 'Error, name is required' });
132
+ }
133
+ try {
134
+ const newJob = new TestJob({ name });
135
+ await newJob.save();
136
+ res.status(201).json({ message: 'job created with success', job: newJob });
137
+ }
138
+ catch (err) {
139
+ console.error('error when creating job : ', err);
140
+ res.status(500).json({ message: 'Internal server error' });
141
+ }
142
+ });
143
+ // Lister tous les jobs
144
+ this.get('/jobs', authenticatedOptions, async (req, res) => {
145
+ try {
146
+ const jobs = await TestJob.find();
147
+ res.status(200).json({ array: jobs });
148
+ }
149
+ catch (err) {
150
+ console.error('error when getting jobs : ', err);
151
+ res.status(500).json({ message: 'Internal server error' });
152
+ }
153
+ });
154
+ // Obtenir un job par son ID
155
+ this.get('/jobs/:id', authenticatedOptions, async (req, res) => {
156
+ const { id } = req.params;
157
+ try {
158
+ const job = await TestJob.findById(id);
159
+ if (!job) {
160
+ return res.status(404).json({ message: 'no job founded with this id' });
161
+ }
162
+ res.status(200).json({ array: job });
163
+ }
164
+ catch (err) {
165
+ console.error('error when getting job : ', err);
166
+ res.status(500).json({ message: 'Internal server error' });
167
+ }
168
+ });
169
+ // Migrer tous les tests avec l'ancien format targetJob
170
+ this.post('/migrate-targetjobs', authenticatedOptions, async (req, res) => {
171
+ try {
172
+ const tests = await Test.find();
173
+ let migratedCount = 0;
174
+ let errorCount = 0;
175
+ for (const test of tests) {
176
+ try {
177
+ // Vérifier si le test a besoin de migration
178
+ if (typeof test.targetJob === 'string') {
179
+ await test.migrateTargetJob();
180
+ migratedCount++;
181
+ }
182
+ }
183
+ catch (error) {
184
+ console.error(`Erreur lors de la migration du test ${test._id}:`, error);
185
+ errorCount++;
186
+ }
187
+ }
188
+ res.status(200).json({
189
+ message: `Migration terminée. ${migratedCount} tests migrés, ${errorCount} erreurs.`,
190
+ migratedCount,
191
+ errorCount
192
+ });
193
+ }
194
+ catch (err) {
195
+ console.error('Erreur lors de la migration :', err);
196
+ res.status(500).json({ message: 'Erreur interne du serveur' });
197
+ }
198
+ });
101
199
  // Créer un test
102
200
  this.post('/test', authenticatedOptions, async (req, res) => {
103
201
  const { title, description, targetJob, seniorityLevel, categories, state = 'draft' } = req.body;
@@ -108,6 +206,19 @@ class ExamsRouter extends EnduranceRouter {
108
206
  try {
109
207
  const companyId = user?.companyId;
110
208
  const userId = user?._id;
209
+ // Traiter le targetJob - si c'est une string, on cherche ou crée le TestJob
210
+ let targetJobId;
211
+ if (typeof targetJob === 'string') {
212
+ let existingJob = await TestJob.findOne({ name: targetJob });
213
+ if (!existingJob) {
214
+ existingJob = new TestJob({ name: targetJob });
215
+ await existingJob.save();
216
+ }
217
+ targetJobId = existingJob._id;
218
+ }
219
+ else {
220
+ targetJobId = targetJob;
221
+ }
111
222
  const processedCategories = await Promise.all(categories?.map(async (category) => {
112
223
  let existingCategory = await TestCategory.findOne({ name: category.name });
113
224
  if (!existingCategory) {
@@ -123,7 +234,7 @@ class ExamsRouter extends EnduranceRouter {
123
234
  userId,
124
235
  title,
125
236
  description,
126
- targetJob,
237
+ targetJob: targetJobId,
127
238
  seniorityLevel,
128
239
  state,
129
240
  categories: processedCategories
@@ -149,8 +260,20 @@ class ExamsRouter extends EnduranceRouter {
149
260
  test.title = title;
150
261
  if (description)
151
262
  test.description = description;
152
- if (targetJob)
153
- test.targetJob = targetJob;
263
+ if (targetJob) {
264
+ // Traiter le targetJob - si c'est une string, on cherche ou crée le TestJob
265
+ if (typeof targetJob === 'string') {
266
+ let existingJob = await TestJob.findOne({ name: targetJob });
267
+ if (!existingJob) {
268
+ existingJob = new TestJob({ name: targetJob });
269
+ await existingJob.save();
270
+ }
271
+ test.targetJob = existingJob._id;
272
+ }
273
+ else {
274
+ test.targetJob = targetJob;
275
+ }
276
+ }
154
277
  if (seniorityLevel)
155
278
  test.seniorityLevel = seniorityLevel;
156
279
  if (state)
@@ -204,6 +327,8 @@ class ExamsRouter extends EnduranceRouter {
204
327
  if (!test) {
205
328
  return res.status(404).json({ message: 'no test founded with this id' });
206
329
  }
330
+ // Migration automatique si nécessaire
331
+ await migrateTestIfNeeded(test);
207
332
  const questions = [];
208
333
  for (const questionRef of test.questions) {
209
334
  console.log(questionRef);
@@ -213,7 +338,10 @@ class ExamsRouter extends EnduranceRouter {
213
338
  questions.push(question);
214
339
  }
215
340
  }
216
- res.status(200).json({ test, questions });
341
+ // Récupérer le nom du job pour l'affichage
342
+ const testObj = test.toObject();
343
+ testObj.targetJobName = await getJobName(testObj.targetJob);
344
+ res.status(200).json({ test: testObj, questions });
217
345
  }
218
346
  catch (err) {
219
347
  console.error('error when geting test : ', err);
@@ -236,7 +364,15 @@ class ExamsRouter extends EnduranceRouter {
236
364
  const query = {};
237
365
  // Filtres
238
366
  if (targetJob !== 'all') {
239
- query.targetJob = targetJob;
367
+ // Si on filtre par targetJob, on cherche d'abord le TestJob correspondant
368
+ const jobType = await TestJob.findOne({ name: targetJob });
369
+ if (jobType) {
370
+ query.targetJob = jobType._id;
371
+ }
372
+ else {
373
+ // Si le job n'existe pas, on ne retourne aucun résultat
374
+ query.targetJob = null;
375
+ }
240
376
  }
241
377
  if (seniorityLevel !== 'all') {
242
378
  query.seniorityLevel = seniorityLevel;
@@ -246,10 +382,13 @@ class ExamsRouter extends EnduranceRouter {
246
382
  }
247
383
  // Recherche sur testName et targetJob
248
384
  if (search) {
385
+ // Pour la recherche sur targetJob, on cherche d'abord les jobs qui correspondent
386
+ const matchingJobs = await TestJob.find({ name: { $regex: search, $options: 'i' } });
387
+ const jobIds = matchingJobs.map(job => job._id);
249
388
  query.$or = [
250
389
  { title: { $regex: search, $options: 'i' } },
251
390
  { description: { $regex: search, $options: 'i' } },
252
- { targetJob: { $regex: search, $options: 'i' } },
391
+ { targetJob: { $in: jobIds } },
253
392
  { seniorityLevel: { $regex: search, $options: 'i' } }
254
393
  ];
255
394
  }
@@ -267,9 +406,13 @@ class ExamsRouter extends EnduranceRouter {
267
406
  .exec(),
268
407
  Test.countDocuments(query)
269
408
  ]);
270
- // Récupérer les noms des catégories pour chaque test
409
+ // Récupérer les noms des catégories et des jobs pour chaque test
271
410
  const testsWithCategories = await Promise.all(tests.map(async (test) => {
411
+ // Migration automatique si nécessaire
412
+ await migrateTestIfNeeded(test);
272
413
  const testObj = test.toObject();
414
+ // Récupérer le nom du job
415
+ testObj.targetJobName = await getJobName(testObj.targetJob);
273
416
  if (testObj.categories && testObj.categories.length > 0) {
274
417
  const categoriesWithNames = await Promise.all(testObj.categories.map(async (category) => {
275
418
  const categoryDoc = await TestCategory.findById(category.categoryId);
@@ -478,8 +621,9 @@ class ExamsRouter extends EnduranceRouter {
478
621
  }
479
622
  const otherQuestionsIds = test.questions.map(question => question.questionId);
480
623
  const otherQuestions = await TestQuestion.find({ _id: { $in: otherQuestionsIds } });
624
+ const jobName = await getJobName(test.targetJob);
481
625
  const generatedQuestion = await generateLiveMessageAssistant(process.env.OPENAI_ASSISTANT_ID_CREATE_QUESTION || '', 'createQuestion', {
482
- job: test.targetJob,
626
+ job: jobName,
483
627
  seniority: test.seniorityLevel,
484
628
  questionType,
485
629
  category,
@@ -2,6 +2,25 @@ import { EnduranceRouter, EnduranceAuthMiddleware, enduranceEmitter, enduranceEv
2
2
  import CandidateModel from '../models/candidate.model.js';
3
3
  import TestResult, { TestState } from '../models/test-result.model.js';
4
4
  import Test from '../models/test.model.js';
5
+ import TestJob from '../models/test-job.model.js';
6
+ // Fonction utilitaire pour récupérer le nom du job
7
+ async function getJobName(targetJob) {
8
+ // Si c'est déjà une string (ancien format), on la retourne directement
9
+ if (typeof targetJob === 'string') {
10
+ return targetJob;
11
+ }
12
+ // Si c'est un ObjectId, on récupère le job
13
+ if (targetJob && typeof targetJob === 'object' && targetJob._id) {
14
+ const job = await TestJob.findById(targetJob._id);
15
+ return job ? job.name : 'Job inconnu';
16
+ }
17
+ // Si c'est juste un ObjectId
18
+ if (targetJob && typeof targetJob === 'object' && targetJob.toString) {
19
+ const job = await TestJob.findById(targetJob);
20
+ return job ? job.name : 'Job inconnu';
21
+ }
22
+ return 'Job inconnu';
23
+ }
5
24
  class ResultRouter extends EnduranceRouter {
6
25
  constructor() {
7
26
  super(EnduranceAuthMiddleware.getInstance());
@@ -81,7 +100,7 @@ class ResultRouter extends EnduranceRouter {
81
100
  ? {
82
101
  title: test.title,
83
102
  description: test.description,
84
- targetJob: test.targetJob,
103
+ targetJob: await getJobName(test.targetJob),
85
104
  seniorityLevel: test.seniorityLevel,
86
105
  categories: categoriesWithNames
87
106
  }
@@ -129,11 +148,14 @@ class ResultRouter extends EnduranceRouter {
129
148
  const questions = await TestQuestion.find({ _id: { $in: (test.questions || []).map((q) => q.questionId) } }).lean();
130
149
  const maxTime = questions.reduce((sum, q) => sum + (q.time || 0), 0);
131
150
  const numberOfQuestions = questions.length;
151
+ // Récupérer le nom du job
152
+ const targetJobName = await getJobName(test.targetJob);
132
153
  // Construire la réponse sans les questions
133
154
  const { questions: _questions, // on retire les questions
134
155
  ...testWithoutQuestions } = test;
135
156
  return res.json({
136
157
  ...testWithoutQuestions,
158
+ targetJobName,
137
159
  categories: categoriesWithNames,
138
160
  maxTime,
139
161
  numberOfQuestions
@@ -312,18 +334,25 @@ class ResultRouter extends EnduranceRouter {
312
334
  score: 0,
313
335
  comment: ''
314
336
  });
337
+ // Marquer explicitement le champ responses comme modifié
338
+ result.markModified('responses');
339
+ console.log('Avant sauvegarde - Responses:', result.responses);
315
340
  // Vérifier si c'était la dernière question
316
341
  const totalQuestions = test.questions.length;
317
342
  const answeredQuestions = result.responses.length;
318
343
  if (answeredQuestions === totalQuestions) {
319
344
  result.state = TestState.Finish;
320
- // Déclencher la correction automatique
321
- await enduranceEmitter.emit(enduranceEventTypes.CORRECT_TEST, result);
322
345
  }
323
346
  else {
324
347
  result.state = TestState.InProgress;
325
348
  }
326
- await result.save();
349
+ // Sauvegarder d'abord la réponse
350
+ const savedResult = await result.save();
351
+ console.log('Après sauvegarde - Responses:', savedResult.responses);
352
+ // Déclencher la correction automatique seulement après la sauvegarde
353
+ if (answeredQuestions === totalQuestions) {
354
+ await enduranceEmitter.emit(enduranceEventTypes.CORRECT_TEST, savedResult);
355
+ }
327
356
  return res.status(200).json({
328
357
  message: 'Réponse enregistrée',
329
358
  response,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@programisto/edrm-exams",
4
- "version": "0.2.13",
4
+ "version": "0.3.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },