@programisto/edrm-exams 0.3.10 → 0.3.12
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/modules/edrm-exams/lib/score-utils.d.ts +18 -0
- package/dist/modules/edrm-exams/lib/score-utils.js +48 -0
- package/dist/modules/edrm-exams/listeners/correct.listener.js +5 -0
- package/dist/modules/edrm-exams/models/test-question.model.d.ts +4 -2
- package/dist/modules/edrm-exams/models/test-question.model.js +6 -0
- package/dist/modules/edrm-exams/models/test-result.model.d.ts +6 -0
- package/dist/modules/edrm-exams/models/test-result.model.js +5 -0
- package/dist/modules/edrm-exams/routes/exams.router.js +75 -21
- package/package.json +1 -1
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CategoryScore } from '../models/test-result.model.js';
|
|
2
|
+
export interface ComputeScoresResult {
|
|
3
|
+
score: number;
|
|
4
|
+
scoresByCategory: CategoryScore[];
|
|
5
|
+
}
|
|
6
|
+
/** Objet ayant au minimum testId et responses (document TestResult ou équivalent). */
|
|
7
|
+
export interface ResultWithResponses {
|
|
8
|
+
testId: unknown;
|
|
9
|
+
responses?: Array<{
|
|
10
|
+
questionId: unknown;
|
|
11
|
+
score?: number;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Calcule le score global et les sous-scores par catégorie à partir des réponses d'un TestResult.
|
|
16
|
+
* Les questions sans categoryId contribuent au score global uniquement (pas d'entrée dans scoresByCategory).
|
|
17
|
+
*/
|
|
18
|
+
export declare function computeScoresByCategory(result: ResultWithResponses): Promise<ComputeScoresResult>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import Test from '../models/test.model.js';
|
|
2
|
+
import TestQuestion from '../models/test-question.model.js';
|
|
3
|
+
/**
|
|
4
|
+
* Calcule le score global et les sous-scores par catégorie à partir des réponses d'un TestResult.
|
|
5
|
+
* Les questions sans categoryId contribuent au score global uniquement (pas d'entrée dans scoresByCategory).
|
|
6
|
+
*/
|
|
7
|
+
export async function computeScoresByCategory(result) {
|
|
8
|
+
const responses = result.responses || [];
|
|
9
|
+
let score = 0;
|
|
10
|
+
const categoryMap = new Map();
|
|
11
|
+
const test = await Test.findById(result.testId).lean();
|
|
12
|
+
if (!test) {
|
|
13
|
+
return { score: 0, scoresByCategory: [] };
|
|
14
|
+
}
|
|
15
|
+
for (const res of responses) {
|
|
16
|
+
const questionId = res.questionId?.toString?.() ?? res.questionId;
|
|
17
|
+
if (!questionId)
|
|
18
|
+
continue;
|
|
19
|
+
const question = await TestQuestion.findById(questionId).lean();
|
|
20
|
+
if (!question)
|
|
21
|
+
continue;
|
|
22
|
+
const responseScore = typeof res.score === 'number' && !isNaN(res.score) ? res.score : 0;
|
|
23
|
+
const questionMaxScore = typeof question.maxScore === 'number' && !isNaN(question.maxScore) ? question.maxScore : 0;
|
|
24
|
+
score += responseScore;
|
|
25
|
+
const catId = question.categoryId;
|
|
26
|
+
if (catId) {
|
|
27
|
+
const key = catId.toString?.() ?? String(catId);
|
|
28
|
+
const existing = categoryMap.get(key);
|
|
29
|
+
if (existing) {
|
|
30
|
+
existing.score += responseScore;
|
|
31
|
+
existing.maxScore += questionMaxScore;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
categoryMap.set(key, {
|
|
35
|
+
categoryId: catId,
|
|
36
|
+
score: responseScore,
|
|
37
|
+
maxScore: questionMaxScore
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const scoresByCategory = Array.from(categoryMap.values()).map(({ categoryId, score: catScore, maxScore: catMax }) => ({
|
|
43
|
+
categoryId: categoryId,
|
|
44
|
+
score: catScore,
|
|
45
|
+
maxScore: catMax
|
|
46
|
+
}));
|
|
47
|
+
return { score, scoresByCategory };
|
|
48
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { enduranceListener, enduranceEventTypes, enduranceEmitter } from '@programisto/endurance';
|
|
2
2
|
import TestQuestion from '../models/test-question.model.js';
|
|
3
3
|
import { generateLiveMessageAssistant } from '../lib/openai.js';
|
|
4
|
+
import { computeScoresByCategory } from '../lib/score-utils.js';
|
|
4
5
|
import TestResult, { TestState } from '../models/test-result.model.js';
|
|
5
6
|
import CandidateModel from '../models/candidate.model.js';
|
|
6
7
|
import ContactModel from '../models/contact.model.js';
|
|
@@ -97,6 +98,9 @@ async function correctTest(options) {
|
|
|
97
98
|
result.state = TestState.Finish;
|
|
98
99
|
// Forcer la sauvegarde des sous-documents responses
|
|
99
100
|
result.markModified('responses');
|
|
101
|
+
// Calculer les sous-scores par catégorie
|
|
102
|
+
const { scoresByCategory } = await computeScoresByCategory(result);
|
|
103
|
+
result.scoresByCategory = scoresByCategory;
|
|
100
104
|
// Calculer le pourcentage de score en évitant la division par zéro
|
|
101
105
|
let scorePercentage = 0;
|
|
102
106
|
if (maxScore > 0) {
|
|
@@ -115,6 +119,7 @@ async function correctTest(options) {
|
|
|
115
119
|
$set: {
|
|
116
120
|
responses: result.responses,
|
|
117
121
|
score: finalscore,
|
|
122
|
+
scoresByCategory: result.scoresByCategory,
|
|
118
123
|
state: result.state
|
|
119
124
|
}
|
|
120
125
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EnduranceSchema } from '@programisto/endurance';
|
|
2
|
+
import TestCategory from './test-category.models.js';
|
|
2
3
|
declare enum QuestionType {
|
|
3
4
|
MCQ = "MCQ",
|
|
4
5
|
FreeQuestion = "free question",
|
|
@@ -19,7 +20,8 @@ declare class TestQuestion extends EnduranceSchema {
|
|
|
19
20
|
possibleResponses: PossibleResponse[];
|
|
20
21
|
time: number;
|
|
21
22
|
textType: TextType;
|
|
22
|
-
|
|
23
|
+
categoryId?: typeof TestCategory;
|
|
24
|
+
static getModel(): import("@typegoose/typegoose").ReturnModelType<typeof TestQuestion, import("@typegoose/typegoose/lib/types.js").BeAnObject>;
|
|
23
25
|
}
|
|
24
|
-
declare const TestQuestionModel: import("@typegoose/typegoose").ReturnModelType<typeof TestQuestion, import("@typegoose/typegoose/lib/types").BeAnObject>;
|
|
26
|
+
declare const TestQuestionModel: import("@typegoose/typegoose").ReturnModelType<typeof TestQuestion, import("@typegoose/typegoose/lib/types.js").BeAnObject>;
|
|
25
27
|
export default TestQuestionModel;
|
|
@@ -8,6 +8,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
10
|
import { EnduranceSchema, EnduranceModelType } from '@programisto/endurance';
|
|
11
|
+
import TestCategory from './test-category.models.js';
|
|
11
12
|
var QuestionType;
|
|
12
13
|
(function (QuestionType) {
|
|
13
14
|
// eslint-disable-next-line no-unused-vars
|
|
@@ -31,6 +32,7 @@ let TestQuestion = class TestQuestion extends EnduranceSchema {
|
|
|
31
32
|
possibleResponses;
|
|
32
33
|
time;
|
|
33
34
|
textType;
|
|
35
|
+
categoryId;
|
|
34
36
|
static getModel() {
|
|
35
37
|
return TestQuestionModel;
|
|
36
38
|
}
|
|
@@ -59,6 +61,10 @@ __decorate([
|
|
|
59
61
|
EnduranceModelType.prop({ required: false, enum: TextType, default: TextType.Text }),
|
|
60
62
|
__metadata("design:type", String)
|
|
61
63
|
], TestQuestion.prototype, "textType", void 0);
|
|
64
|
+
__decorate([
|
|
65
|
+
EnduranceModelType.prop({ ref: () => TestCategory, required: false }),
|
|
66
|
+
__metadata("design:type", Object)
|
|
67
|
+
], TestQuestion.prototype, "categoryId", void 0);
|
|
62
68
|
TestQuestion = __decorate([
|
|
63
69
|
EnduranceModelType.modelOptions({
|
|
64
70
|
options: {
|
|
@@ -12,12 +12,18 @@ interface Response {
|
|
|
12
12
|
score?: number;
|
|
13
13
|
comment?: string;
|
|
14
14
|
}
|
|
15
|
+
export interface CategoryScore {
|
|
16
|
+
categoryId: ObjectId;
|
|
17
|
+
score: number;
|
|
18
|
+
maxScore: number;
|
|
19
|
+
}
|
|
15
20
|
declare class TestResult extends EnduranceSchema {
|
|
16
21
|
testId: typeof Test;
|
|
17
22
|
candidateId: typeof User;
|
|
18
23
|
state: TestState;
|
|
19
24
|
responses: Response[];
|
|
20
25
|
score?: number;
|
|
26
|
+
scoresByCategory?: CategoryScore[];
|
|
21
27
|
startTime?: Date;
|
|
22
28
|
endTime?: Date;
|
|
23
29
|
static getModel(): import("@typegoose/typegoose").ReturnModelType<typeof TestResult, import("@typegoose/typegoose/lib/types.js").BeAnObject>;
|
|
@@ -25,6 +25,7 @@ let TestResult = class TestResult extends EnduranceSchema {
|
|
|
25
25
|
state;
|
|
26
26
|
responses;
|
|
27
27
|
score;
|
|
28
|
+
scoresByCategory;
|
|
28
29
|
startTime;
|
|
29
30
|
endTime;
|
|
30
31
|
static getModel() {
|
|
@@ -51,6 +52,10 @@ __decorate([
|
|
|
51
52
|
EnduranceModelType.prop(),
|
|
52
53
|
__metadata("design:type", Number)
|
|
53
54
|
], TestResult.prototype, "score", void 0);
|
|
55
|
+
__decorate([
|
|
56
|
+
EnduranceModelType.prop({ type: [Object], required: false }),
|
|
57
|
+
__metadata("design:type", Array)
|
|
58
|
+
], TestResult.prototype, "scoresByCategory", void 0);
|
|
54
59
|
__decorate([
|
|
55
60
|
EnduranceModelType.prop(),
|
|
56
61
|
__metadata("design:type", Date)
|
|
@@ -7,6 +7,7 @@ import TestJob from '../models/test-job.model.js';
|
|
|
7
7
|
import Candidate from '../models/candidate.model.js';
|
|
8
8
|
import ContactModel from '../models/contact.model.js';
|
|
9
9
|
import { generateLiveMessage, generateLiveMessageAssistant } from '../lib/openai.js';
|
|
10
|
+
import { computeScoresByCategory } from '../lib/score-utils.js';
|
|
10
11
|
// Fonction utilitaire pour récupérer le nom du job
|
|
11
12
|
async function getJobName(targetJob) {
|
|
12
13
|
// Si c'est déjà une string (ancien format), on la retourne directement
|
|
@@ -25,6 +26,23 @@ async function getJobName(targetJob) {
|
|
|
25
26
|
}
|
|
26
27
|
return 'Job inconnu';
|
|
27
28
|
}
|
|
29
|
+
/** Entité par défaut : les tests sans entityId lui sont rattachés (slug programisto/progamisto ou isDefault). */
|
|
30
|
+
function isDefaultEntity(req) {
|
|
31
|
+
if (!req?.entity)
|
|
32
|
+
return false;
|
|
33
|
+
const slug = req.entity.slug;
|
|
34
|
+
return req.entity.isDefault === true || slug === 'programisto' || slug === 'progamisto';
|
|
35
|
+
}
|
|
36
|
+
/** Vérifie qu'un test appartient à l'entité courante (pour GET/UPDATE/DELETE). Pour l'entité par défaut, un test sans entityId est accepté. */
|
|
37
|
+
function testBelongsToEntity(test, req) {
|
|
38
|
+
if (!req?.entity?._id)
|
|
39
|
+
return true;
|
|
40
|
+
const testEid = test?.entityId?.toString?.() ?? (test?.entityId != null ? String(test.entityId) : '');
|
|
41
|
+
const reqEid = req.entity._id?.toString?.() ?? String(req.entity._id);
|
|
42
|
+
if (isDefaultEntity(req))
|
|
43
|
+
return testEid === reqEid || testEid === '';
|
|
44
|
+
return testEid === reqEid;
|
|
45
|
+
}
|
|
28
46
|
// Fonction pour migrer automatiquement un test si nécessaire
|
|
29
47
|
async function migrateTestIfNeeded(test) {
|
|
30
48
|
if (typeof test.targetJob === 'string') {
|
|
@@ -452,7 +470,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
452
470
|
if (!test) {
|
|
453
471
|
return res.status(404).json({ message: 'Test non trouvé' });
|
|
454
472
|
}
|
|
455
|
-
if (
|
|
473
|
+
if (!testBelongsToEntity(test, req)) {
|
|
456
474
|
return res.status(404).json({ message: 'Test non trouvé' });
|
|
457
475
|
}
|
|
458
476
|
if (title)
|
|
@@ -543,7 +561,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
543
561
|
if (!test) {
|
|
544
562
|
return res.status(404).json({ message: 'Test not found' });
|
|
545
563
|
}
|
|
546
|
-
if (
|
|
564
|
+
if (!testBelongsToEntity(test, req)) {
|
|
547
565
|
return res.status(404).json({ message: 'Test not found' });
|
|
548
566
|
}
|
|
549
567
|
for (let i = 0; i < test.questions.length; i++) {
|
|
@@ -587,7 +605,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
587
605
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
588
606
|
}
|
|
589
607
|
// Vérifier que le test appartient à l'entité courante (multi-entités)
|
|
590
|
-
if (
|
|
608
|
+
if (!testBelongsToEntity(test, req)) {
|
|
591
609
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
592
610
|
}
|
|
593
611
|
// Migration automatique si nécessaire
|
|
@@ -677,9 +695,22 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
677
695
|
const sortOrder = req.query.sortOrder || 'desc';
|
|
678
696
|
// Construction de la requête de recherche
|
|
679
697
|
const query = {};
|
|
680
|
-
// Filtre par
|
|
698
|
+
// Filtre par entité. Entité par défaut (programisto/progamisto ou isDefault) = tests sans entityId inclus.
|
|
681
699
|
if (req.entity?._id) {
|
|
682
|
-
|
|
700
|
+
if (isDefaultEntity(req)) {
|
|
701
|
+
query.$and = query.$and || [];
|
|
702
|
+
query.$and.push({
|
|
703
|
+
$or: [
|
|
704
|
+
{ entityId: req.entity._id },
|
|
705
|
+
{ entityId: req.entity._id.toString?.() ?? String(req.entity._id) },
|
|
706
|
+
{ entityId: { $exists: false } },
|
|
707
|
+
{ entityId: null }
|
|
708
|
+
]
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
query.entityId = req.entity._id;
|
|
713
|
+
}
|
|
683
714
|
}
|
|
684
715
|
// Filtres
|
|
685
716
|
if (targetJob !== 'all') {
|
|
@@ -802,7 +833,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
802
833
|
let test = await Test.findById(testId);
|
|
803
834
|
if (!test)
|
|
804
835
|
return res.status(404).json({ message: 'Test not found' });
|
|
805
|
-
if (
|
|
836
|
+
if (!testBelongsToEntity(test, req)) {
|
|
806
837
|
return res.status(404).json({ message: 'Test not found' });
|
|
807
838
|
}
|
|
808
839
|
test = await Test.findByIdAndUpdate(testId, { $pull: { categories: { categoryId: category._id } } }, { new: true });
|
|
@@ -858,7 +889,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
858
889
|
if (!test) {
|
|
859
890
|
return res.status(404).json({ message: 'Test not found' });
|
|
860
891
|
}
|
|
861
|
-
if (
|
|
892
|
+
if (!testBelongsToEntity(test, req)) {
|
|
862
893
|
return res.status(404).json({ message: 'Test not found' });
|
|
863
894
|
}
|
|
864
895
|
const categoryExists = test.categories.some(cat => cat.categoryId.equals(category._id));
|
|
@@ -931,7 +962,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
931
962
|
if (!test) {
|
|
932
963
|
return res.status(404).json({ message: 'Test not found' });
|
|
933
964
|
}
|
|
934
|
-
if (
|
|
965
|
+
if (!testBelongsToEntity(test, req)) {
|
|
935
966
|
return res.status(404).json({ message: 'Test not found' });
|
|
936
967
|
}
|
|
937
968
|
const questions = [];
|
|
@@ -984,7 +1015,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
984
1015
|
if (!test) {
|
|
985
1016
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
986
1017
|
}
|
|
987
|
-
if (
|
|
1018
|
+
if (!testBelongsToEntity(test, req)) {
|
|
988
1019
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
989
1020
|
}
|
|
990
1021
|
// Supprimer la question du tableau questions en filtrant par questionId
|
|
@@ -1023,7 +1054,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
1023
1054
|
if (!test) {
|
|
1024
1055
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
1025
1056
|
}
|
|
1026
|
-
if (
|
|
1057
|
+
if (!testBelongsToEntity(test, req)) {
|
|
1027
1058
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
1028
1059
|
}
|
|
1029
1060
|
for (const questionId of test.questions) {
|
|
@@ -1062,7 +1093,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
1062
1093
|
*/
|
|
1063
1094
|
this.put('/test/modifyQuestion/:id', authenticatedOptions, async (req, res) => {
|
|
1064
1095
|
const { id } = req.params;
|
|
1065
|
-
const { instruction, maxScore, time, possibleResponses, textType } = req.body;
|
|
1096
|
+
const { instruction, maxScore, time, possibleResponses, textType, categoryId } = req.body;
|
|
1066
1097
|
try {
|
|
1067
1098
|
const question = await TestQuestion.findById(id);
|
|
1068
1099
|
if (!question) {
|
|
@@ -1083,6 +1114,18 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
1083
1114
|
if (possibleResponses) {
|
|
1084
1115
|
question.possibleResponses = possibleResponses;
|
|
1085
1116
|
}
|
|
1117
|
+
if (categoryId !== undefined) {
|
|
1118
|
+
if (categoryId === null || categoryId === '') {
|
|
1119
|
+
question.categoryId = undefined;
|
|
1120
|
+
}
|
|
1121
|
+
else {
|
|
1122
|
+
const category = await TestCategory.findById(categoryId);
|
|
1123
|
+
if (!category) {
|
|
1124
|
+
return res.status(400).json({ message: 'Catégorie non trouvée' });
|
|
1125
|
+
}
|
|
1126
|
+
question.categoryId = categoryId;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1086
1129
|
await question.save();
|
|
1087
1130
|
res.status(200).json({ message: 'question modified with sucess' });
|
|
1088
1131
|
}
|
|
@@ -1129,20 +1172,27 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
1129
1172
|
*/
|
|
1130
1173
|
this.put('/test/addCustomQuestion/:id', authenticatedOptions, async (req, res) => {
|
|
1131
1174
|
const { id } = req.params;
|
|
1132
|
-
const { questionType, instruction, maxScore, time } = req.body;
|
|
1175
|
+
const { questionType, instruction, maxScore, time, categoryId } = req.body;
|
|
1133
1176
|
try {
|
|
1134
1177
|
const test = await Test.findById(id);
|
|
1135
1178
|
if (!test) {
|
|
1136
1179
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
1137
1180
|
}
|
|
1138
|
-
if (
|
|
1181
|
+
if (!testBelongsToEntity(test, req)) {
|
|
1139
1182
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
1140
1183
|
}
|
|
1184
|
+
if (categoryId != null && categoryId !== '') {
|
|
1185
|
+
const category = await TestCategory.findById(categoryId);
|
|
1186
|
+
if (!category) {
|
|
1187
|
+
return res.status(400).json({ message: 'Catégorie non trouvée' });
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1141
1190
|
const question = new TestQuestion({
|
|
1142
1191
|
questionType,
|
|
1143
1192
|
instruction,
|
|
1144
1193
|
maxScore,
|
|
1145
|
-
time
|
|
1194
|
+
time,
|
|
1195
|
+
...(categoryId != null && categoryId !== '' ? { categoryId } : {})
|
|
1146
1196
|
});
|
|
1147
1197
|
await question.save();
|
|
1148
1198
|
test.questions.push({ questionId: question._id, order: test.questions.length });
|
|
@@ -1196,7 +1246,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
1196
1246
|
if (!test) {
|
|
1197
1247
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
1198
1248
|
}
|
|
1199
|
-
if (
|
|
1249
|
+
if (!testBelongsToEntity(test, req)) {
|
|
1200
1250
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
1201
1251
|
}
|
|
1202
1252
|
const otherQuestionsIds = test.questions.map(question => question.questionId);
|
|
@@ -1249,7 +1299,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
1249
1299
|
if (!test) {
|
|
1250
1300
|
return res.status(404).json({ message: 'Test not found' });
|
|
1251
1301
|
}
|
|
1252
|
-
if (
|
|
1302
|
+
if (!testBelongsToEntity(test, req)) {
|
|
1253
1303
|
return res.status(404).json({ message: 'Test not found' });
|
|
1254
1304
|
}
|
|
1255
1305
|
for (let i = test.questions.length - 1; i > 0; i--) {
|
|
@@ -1302,7 +1352,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
1302
1352
|
if (!test) {
|
|
1303
1353
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
1304
1354
|
}
|
|
1305
|
-
if (
|
|
1355
|
+
if (!testBelongsToEntity(test, req)) {
|
|
1306
1356
|
return res.status(404).json({ message: 'no test founded with this id' });
|
|
1307
1357
|
}
|
|
1308
1358
|
test.invitationText = invitationText;
|
|
@@ -1762,6 +1812,8 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
1762
1812
|
response.comment = parsedResult.comment;
|
|
1763
1813
|
}
|
|
1764
1814
|
result.score = finalscore;
|
|
1815
|
+
const { scoresByCategory } = await computeScoresByCategory(result);
|
|
1816
|
+
result.scoresByCategory = scoresByCategory;
|
|
1765
1817
|
await result.save();
|
|
1766
1818
|
res.status(200).json({ data: finalscore });
|
|
1767
1819
|
}
|
|
@@ -1899,7 +1951,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
1899
1951
|
if (!test) {
|
|
1900
1952
|
return res.status(404).json({ message: 'Test non trouvé' });
|
|
1901
1953
|
}
|
|
1902
|
-
if (
|
|
1954
|
+
if (!testBelongsToEntity(test, req)) {
|
|
1903
1955
|
return res.status(404).json({ message: 'Test non trouvé' });
|
|
1904
1956
|
}
|
|
1905
1957
|
let categoriesToUse = [];
|
|
@@ -2034,7 +2086,7 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
2034
2086
|
if (!test) {
|
|
2035
2087
|
return res.status(404).json({ message: 'Test non trouvé' });
|
|
2036
2088
|
}
|
|
2037
|
-
if (
|
|
2089
|
+
if (!testBelongsToEntity(test, req)) {
|
|
2038
2090
|
return res.status(404).json({ message: 'Test non trouvé' });
|
|
2039
2091
|
}
|
|
2040
2092
|
// Construction de la requête
|
|
@@ -2320,8 +2372,10 @@ class ExamsRouter extends EnduranceRouter {
|
|
|
2320
2372
|
response.score = score;
|
|
2321
2373
|
if (typeof comment === 'string')
|
|
2322
2374
|
response.comment = comment;
|
|
2323
|
-
// Recalculer le score global
|
|
2324
|
-
|
|
2375
|
+
// Recalculer le score global et les sous-scores par catégorie
|
|
2376
|
+
const { score: globalScore, scoresByCategory } = await computeScoresByCategory(result);
|
|
2377
|
+
result.score = globalScore;
|
|
2378
|
+
result.scoresByCategory = scoresByCategory;
|
|
2325
2379
|
await result.save();
|
|
2326
2380
|
return res.status(200).json({
|
|
2327
2381
|
message: 'Correction manuelle enregistrée',
|