727 lines
20 KiB
TypeScript
727 lines
20 KiB
TypeScript
import { getPool } from "./database";
|
|
import { userService } from "./user-service";
|
|
import {
|
|
UserEvaluation,
|
|
UserProfile,
|
|
CategoryEvaluation,
|
|
SkillEvaluation,
|
|
SkillLevel,
|
|
} from "../lib/types";
|
|
|
|
export class EvaluationService {
|
|
/**
|
|
* Charge une évaluation utilisateur directement par UUID
|
|
*/
|
|
async loadUserEvaluationByUuid(
|
|
userUuid: string
|
|
): Promise<UserEvaluation | null> {
|
|
if (!userUuid) {
|
|
return null;
|
|
}
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
// Trouver l'utilisateur et son évaluation par UUID
|
|
const userQuery = `
|
|
SELECT u.*, ue.id as user_evaluation_id, ue.last_updated
|
|
FROM users u
|
|
LEFT JOIN user_evaluations ue ON u.uuid_id = ue.user_uuid
|
|
WHERE u.uuid_id = $1
|
|
`;
|
|
|
|
const userResult = await client.query(userQuery, [userUuid]);
|
|
|
|
if (
|
|
userResult.rows.length === 0 ||
|
|
!userResult.rows[0].user_evaluation_id
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const userData = userResult.rows[0];
|
|
const userEvaluationId = userData.user_evaluation_id;
|
|
|
|
// Charger directement les skills évaluées avec leurs catégories
|
|
const skillsQuery = `
|
|
SELECT
|
|
sc.name as category_name,
|
|
se.skill_id,
|
|
se.level,
|
|
se.can_mentor,
|
|
se.wants_to_learn,
|
|
se.is_selected
|
|
FROM skill_evaluations se
|
|
JOIN skills s ON se.skill_id = s.id
|
|
JOIN skill_categories sc ON s.category_id = sc.id
|
|
WHERE se.user_evaluation_id = $1
|
|
ORDER BY sc.name, s.name
|
|
`;
|
|
|
|
const skillsResult = await client.query(skillsQuery, [userEvaluationId]);
|
|
|
|
// Grouper par catégorie
|
|
const categoriesMap = new Map<string, CategoryEvaluation>();
|
|
|
|
for (const row of skillsResult.rows) {
|
|
const categoryName = row.category_name;
|
|
|
|
if (!categoriesMap.has(categoryName)) {
|
|
categoriesMap.set(categoryName, {
|
|
category: categoryName,
|
|
selectedSkillIds: [],
|
|
skills: [],
|
|
});
|
|
}
|
|
|
|
const catEval = categoriesMap.get(categoryName)!;
|
|
|
|
// Ajouter aux skills sélectionnées si is_selected = true
|
|
if (row.is_selected) {
|
|
catEval.selectedSkillIds.push(row.skill_id);
|
|
}
|
|
|
|
// Ajouter aux évaluations si elle est sélectionnée (donc évaluée)
|
|
if (row.is_selected) {
|
|
catEval.skills.push({
|
|
skillId: row.skill_id,
|
|
level: row.level,
|
|
canMentor: row.can_mentor,
|
|
wantsToLearn: row.wants_to_learn,
|
|
});
|
|
}
|
|
}
|
|
|
|
const evaluations: CategoryEvaluation[] = Array.from(
|
|
categoriesMap.values()
|
|
);
|
|
|
|
return {
|
|
profile: {
|
|
firstName: userData.first_name,
|
|
lastName: userData.last_name,
|
|
teamId: userData.team_id,
|
|
},
|
|
evaluations,
|
|
lastUpdated: userData.last_updated.toISOString(),
|
|
};
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sauvegarde une évaluation utilisateur complète (version UUID)
|
|
*/
|
|
async saveUserEvaluationUuid(evaluation: UserEvaluation): Promise<void> {
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
await client.query("BEGIN");
|
|
|
|
// 1. Récupérer le userUuid depuis le profile de l'évaluation
|
|
const existingUser = await userService.findUserByProfile(evaluation.profile);
|
|
if (!existingUser) {
|
|
throw new Error("Utilisateur non trouvé");
|
|
}
|
|
const userUuid = existingUser.uuid;
|
|
|
|
// 2. Upsert user_evaluation avec user_uuid
|
|
const userEvalQuery = `
|
|
INSERT INTO user_evaluations (user_uuid, last_updated)
|
|
VALUES ($1, $2)
|
|
ON CONFLICT (user_uuid)
|
|
DO UPDATE SET last_updated = $2
|
|
RETURNING id
|
|
`;
|
|
|
|
const userEvalResult = await client.query(userEvalQuery, [
|
|
userUuid,
|
|
new Date(evaluation.lastUpdated),
|
|
]);
|
|
|
|
const userEvaluationId = userEvalResult.rows[0].id;
|
|
|
|
// 3. Sauvegarde chaque catégorie d'évaluation
|
|
for (const catEval of evaluation.evaluations) {
|
|
await this.saveSkillEvaluations(client, userEvaluationId, catEval);
|
|
}
|
|
|
|
await client.query("COMMIT");
|
|
} catch (error) {
|
|
await client.query("ROLLBACK");
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sauvegarde une évaluation utilisateur complète (legacy)
|
|
*/
|
|
async saveUserEvaluation(evaluation: UserEvaluation): Promise<void> {
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
await client.query("BEGIN");
|
|
|
|
// 1. Upsert user
|
|
const userId = await userService.upsertUser(evaluation.profile);
|
|
|
|
// 2. Upsert user_evaluation - d'abord supprimer l'ancienne si elle existe
|
|
await client.query("DELETE FROM user_evaluations WHERE user_id = $1", [
|
|
userId,
|
|
]);
|
|
|
|
const userEvalQuery = `
|
|
INSERT INTO user_evaluations (user_id, last_updated)
|
|
VALUES ($1, $2)
|
|
RETURNING id
|
|
`;
|
|
|
|
const userEvalResult = await client.query(userEvalQuery, [
|
|
userId,
|
|
new Date(evaluation.lastUpdated),
|
|
]);
|
|
|
|
const userEvaluationId = userEvalResult.rows[0].id;
|
|
|
|
// 4. Sauvegarder les nouvelles évaluations directement
|
|
for (const catEval of evaluation.evaluations) {
|
|
await this.saveSkillEvaluations(client, userEvaluationId, catEval);
|
|
}
|
|
|
|
await client.query("COMMIT");
|
|
} catch (error) {
|
|
await client.query("ROLLBACK");
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sauvegarde les évaluations de skills directement
|
|
*/
|
|
private async saveSkillEvaluations(
|
|
client: any,
|
|
userEvaluationId: number,
|
|
catEval: CategoryEvaluation
|
|
): Promise<void> {
|
|
// ✅ SOLUTION : BULK INSERT pour éviter les requêtes N+1
|
|
const values = [];
|
|
const params = [];
|
|
let paramIndex = 1;
|
|
|
|
// Préparer toutes les valeurs pour les skills sélectionnées
|
|
for (const skillId of catEval.selectedSkillIds) {
|
|
const isEvaluated = catEval.skills.some(
|
|
(se) => se.skillId === skillId && se.level !== null
|
|
);
|
|
|
|
if (!isEvaluated) {
|
|
values.push(
|
|
`($${paramIndex++}, $${paramIndex++}, 'never'::skill_level_enum, true, false, false)`
|
|
);
|
|
params.push(userEvaluationId, skillId);
|
|
}
|
|
}
|
|
|
|
// Préparer toutes les valeurs pour les évaluations de skills
|
|
for (const skillEval of catEval.skills) {
|
|
if (skillEval.level !== null) {
|
|
values.push(
|
|
`($${paramIndex++}, $${paramIndex++}, $${paramIndex++}, true, $${paramIndex++}, $${paramIndex++})`
|
|
);
|
|
params.push(
|
|
userEvaluationId,
|
|
skillEval.skillId,
|
|
skillEval.level,
|
|
skillEval.canMentor || false,
|
|
skillEval.wantsToLearn || false
|
|
);
|
|
}
|
|
}
|
|
|
|
// Exécuter le BULK INSERT si on a des valeurs
|
|
if (values.length > 0) {
|
|
const bulkQuery = `
|
|
INSERT INTO skill_evaluations
|
|
(user_evaluation_id, skill_id, level, is_selected, can_mentor, wants_to_learn)
|
|
VALUES ${values.join(", ")}
|
|
`;
|
|
await client.query(bulkQuery, params);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Charge une évaluation utilisateur
|
|
*/
|
|
async loadUserEvaluation(
|
|
profile: UserProfile
|
|
): Promise<UserEvaluation | null> {
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
// Trouver l'utilisateur par firstName + lastName (équipe peut avoir changé)
|
|
const userQuery = `
|
|
SELECT u.*, ue.id as user_evaluation_id, ue.last_updated
|
|
FROM users u
|
|
LEFT JOIN user_evaluations ue ON u.uuid_id = ue.user_uuid
|
|
WHERE u.first_name = $1 AND u.last_name = $2
|
|
`;
|
|
|
|
const userResult = await client.query(userQuery, [
|
|
profile.firstName,
|
|
profile.lastName,
|
|
]);
|
|
|
|
if (
|
|
userResult.rows.length === 0 ||
|
|
!userResult.rows[0].user_evaluation_id
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const userData = userResult.rows[0];
|
|
const userEvaluationId = userData.user_evaluation_id;
|
|
|
|
// Charger directement les skills évaluées avec leurs catégories
|
|
const skillsQuery = `
|
|
SELECT
|
|
sc.name as category_name,
|
|
se.skill_id,
|
|
se.level,
|
|
se.can_mentor,
|
|
se.wants_to_learn,
|
|
se.is_selected
|
|
FROM skill_evaluations se
|
|
JOIN skills s ON se.skill_id = s.id
|
|
JOIN skill_categories sc ON s.category_id = sc.id
|
|
WHERE se.user_evaluation_id = $1
|
|
ORDER BY sc.name, s.name
|
|
`;
|
|
|
|
const skillsResult = await client.query(skillsQuery, [userEvaluationId]);
|
|
|
|
// Grouper par catégorie
|
|
const categoriesMap = new Map<string, CategoryEvaluation>();
|
|
|
|
for (const row of skillsResult.rows) {
|
|
const categoryName = row.category_name;
|
|
|
|
if (!categoriesMap.has(categoryName)) {
|
|
categoriesMap.set(categoryName, {
|
|
category: categoryName,
|
|
selectedSkillIds: [],
|
|
skills: [],
|
|
});
|
|
}
|
|
|
|
const catEval = categoriesMap.get(categoryName)!;
|
|
|
|
// Ajouter aux skills sélectionnées si is_selected = true
|
|
if (row.is_selected) {
|
|
catEval.selectedSkillIds.push(row.skill_id);
|
|
}
|
|
|
|
// Ajouter aux évaluations si elle est sélectionnée (donc évaluée)
|
|
if (row.is_selected) {
|
|
catEval.skills.push({
|
|
skillId: row.skill_id,
|
|
level: row.level,
|
|
canMentor: row.can_mentor,
|
|
wantsToLearn: row.wants_to_learn,
|
|
});
|
|
}
|
|
}
|
|
|
|
const evaluations: CategoryEvaluation[] = Array.from(
|
|
categoriesMap.values()
|
|
);
|
|
|
|
return {
|
|
profile,
|
|
evaluations,
|
|
lastUpdated: userData.last_updated.toISOString(),
|
|
};
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Met à jour le niveau d'une skill (version UUID)
|
|
*/
|
|
async updateSkillLevelUuid(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string,
|
|
level: SkillLevel
|
|
): Promise<void> {
|
|
await this.updateSkillPropertyUuid(
|
|
profile,
|
|
category,
|
|
skillId,
|
|
"level",
|
|
level
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Met à jour le statut mentor d'une skill (version UUID)
|
|
*/
|
|
async updateSkillMentorStatusUuid(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string,
|
|
canMentor: boolean
|
|
): Promise<void> {
|
|
await this.updateSkillPropertyUuid(
|
|
profile,
|
|
category,
|
|
skillId,
|
|
"can_mentor",
|
|
canMentor
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Met à jour le statut apprentissage d'une skill (version UUID)
|
|
*/
|
|
async updateSkillLearningStatusUuid(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string,
|
|
wantsToLearn: boolean
|
|
): Promise<void> {
|
|
await this.updateSkillPropertyUuid(
|
|
profile,
|
|
category,
|
|
skillId,
|
|
"wants_to_learn",
|
|
wantsToLearn
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Méthode utilitaire pour mettre à jour une propriété de skill (version UUID)
|
|
*/
|
|
private async updateSkillPropertyUuid(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string,
|
|
property: string,
|
|
value: any
|
|
): Promise<void> {
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
await client.query("BEGIN");
|
|
|
|
// Trouver l'utilisateur existant au lieu d'en créer un nouveau
|
|
const existingUser = await userService.findUserByProfile(profile);
|
|
if (!existingUser) {
|
|
throw new Error(
|
|
"Utilisateur non trouvé. Veuillez vous connecter avec votre email et mot de passe."
|
|
);
|
|
}
|
|
|
|
const userUuid = existingUser.uuid;
|
|
|
|
// Upsert user_evaluation avec user_uuid
|
|
const userEvalResult = await client.query(
|
|
`
|
|
INSERT INTO user_evaluations (user_uuid, last_updated)
|
|
VALUES ($1, CURRENT_TIMESTAMP)
|
|
ON CONFLICT (user_uuid)
|
|
DO UPDATE SET last_updated = CURRENT_TIMESTAMP
|
|
RETURNING id
|
|
`,
|
|
[userUuid]
|
|
);
|
|
|
|
const userEvaluationId = userEvalResult.rows[0].id;
|
|
|
|
// Upsert skill evaluation avec gestion conditionnelle du level
|
|
let updateQuery: string;
|
|
let queryParams: any[];
|
|
|
|
if (property === "level") {
|
|
// Si on met à jour le level, utiliser la valeur fournie
|
|
updateQuery = `
|
|
INSERT INTO skill_evaluations (user_evaluation_id, skill_id, level, is_selected)
|
|
VALUES ($1, $2, $3, true)
|
|
ON CONFLICT (user_evaluation_id, skill_id)
|
|
DO UPDATE SET level = $3, is_selected = true
|
|
`;
|
|
queryParams = [userEvaluationId, skillId, value];
|
|
} else {
|
|
// Si on met à jour une autre propriété, level par défaut = 'never'
|
|
updateQuery = `
|
|
INSERT INTO skill_evaluations (user_evaluation_id, skill_id, level, ${property}, is_selected)
|
|
VALUES ($1, $2, 'never', $3, true)
|
|
ON CONFLICT (user_evaluation_id, skill_id)
|
|
DO UPDATE SET ${property} = $3, is_selected = true
|
|
`;
|
|
queryParams = [userEvaluationId, skillId, value];
|
|
}
|
|
|
|
await client.query(updateQuery, queryParams);
|
|
|
|
await client.query("COMMIT");
|
|
} catch (error) {
|
|
await client.query("ROLLBACK");
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Met à jour le niveau d'une skill (legacy)
|
|
*/
|
|
async updateSkillLevel(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string,
|
|
level: SkillLevel
|
|
): Promise<void> {
|
|
await this.upsertSkillEvaluation(profile, skillId, { level });
|
|
}
|
|
|
|
/**
|
|
* Met à jour le statut de mentorat d'une skill
|
|
*/
|
|
async updateSkillMentorStatus(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string,
|
|
canMentor: boolean
|
|
): Promise<void> {
|
|
await this.upsertSkillEvaluation(profile, skillId, { canMentor });
|
|
}
|
|
|
|
/**
|
|
* Met à jour le statut d'apprentissage d'une skill
|
|
*/
|
|
async updateSkillLearningStatus(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string,
|
|
wantsToLearn: boolean
|
|
): Promise<void> {
|
|
await this.upsertSkillEvaluation(profile, skillId, { wantsToLearn });
|
|
}
|
|
|
|
/**
|
|
* Méthode utilitaire pour mettre à jour ou créer une évaluation de skill
|
|
*/
|
|
private async upsertSkillEvaluation(
|
|
profile: UserProfile,
|
|
skillId: string,
|
|
updates: {
|
|
level?: SkillLevel;
|
|
canMentor?: boolean;
|
|
wantsToLearn?: boolean;
|
|
isSelected?: boolean;
|
|
}
|
|
): Promise<void> {
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
await client.query("BEGIN");
|
|
|
|
const userId = await userService.upsertUser(profile);
|
|
|
|
// Upsert user_evaluation
|
|
const userEvalResult = await client.query(
|
|
`
|
|
INSERT INTO user_evaluations (user_id, last_updated)
|
|
VALUES ($1, CURRENT_TIMESTAMP)
|
|
ON CONFLICT (user_id)
|
|
DO UPDATE SET last_updated = CURRENT_TIMESTAMP
|
|
RETURNING id
|
|
`,
|
|
[userId]
|
|
);
|
|
|
|
const userEvaluationId = userEvalResult.rows[0].id;
|
|
|
|
// Upsert skill evaluation avec valeurs par défaut
|
|
const query = `
|
|
INSERT INTO skill_evaluations (
|
|
user_evaluation_id,
|
|
skill_id,
|
|
level,
|
|
can_mentor,
|
|
wants_to_learn,
|
|
is_selected
|
|
)
|
|
VALUES ($1, $2,
|
|
COALESCE($3::skill_level_enum, 'never'::skill_level_enum),
|
|
COALESCE($4, false),
|
|
COALESCE($5, false),
|
|
COALESCE($6, true)
|
|
)
|
|
ON CONFLICT (user_evaluation_id, skill_id)
|
|
DO UPDATE SET
|
|
level = CASE WHEN $3 IS NOT NULL THEN $3::skill_level_enum ELSE skill_evaluations.level END,
|
|
can_mentor = CASE WHEN $4 IS NOT NULL THEN $4 ELSE skill_evaluations.can_mentor END,
|
|
wants_to_learn = CASE WHEN $5 IS NOT NULL THEN $5 ELSE skill_evaluations.wants_to_learn END,
|
|
is_selected = CASE WHEN $6 IS NOT NULL THEN $6 ELSE skill_evaluations.is_selected END,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
`;
|
|
|
|
await client.query(query, [
|
|
userEvaluationId,
|
|
skillId,
|
|
updates.level || null,
|
|
updates.canMentor !== undefined ? updates.canMentor : null,
|
|
updates.wantsToLearn !== undefined ? updates.wantsToLearn : null,
|
|
updates.isSelected !== undefined ? updates.isSelected : null,
|
|
]);
|
|
|
|
await client.query("COMMIT");
|
|
} catch (error) {
|
|
await client.query("ROLLBACK");
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ajoute une skill à l'évaluation (version UUID)
|
|
*/
|
|
async addSkillToEvaluationUuid(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string
|
|
): Promise<void> {
|
|
await this.updateSkillPropertyUuid(
|
|
profile,
|
|
category,
|
|
skillId,
|
|
"level",
|
|
"never" // Valeur par défaut pour une skill nouvellement sélectionnée
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Supprime une skill de l'évaluation (version UUID)
|
|
*/
|
|
async removeSkillFromEvaluationUuid(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string
|
|
): Promise<void> {
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
await client.query("BEGIN");
|
|
|
|
// Récupérer le userUuid depuis le profile
|
|
const existingUser = await userService.findUserByProfile(profile);
|
|
if (!existingUser) {
|
|
throw new Error("Utilisateur non trouvé");
|
|
}
|
|
const userUuid = existingUser.uuid;
|
|
|
|
// Supprimer directement la skill evaluation
|
|
const deleteQuery = `
|
|
DELETE FROM skill_evaluations
|
|
WHERE user_evaluation_id = (
|
|
SELECT id FROM user_evaluations WHERE user_uuid = $1
|
|
)
|
|
AND skill_id = $2
|
|
`;
|
|
|
|
await client.query(deleteQuery, [userUuid, skillId]);
|
|
|
|
await client.query("COMMIT");
|
|
} catch (error) {
|
|
await client.query("ROLLBACK");
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ajoute une skill à l'évaluation (legacy)
|
|
*/
|
|
async addSkillToEvaluation(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string
|
|
): Promise<void> {
|
|
await this.upsertSkillEvaluation(profile, skillId, {
|
|
isSelected: true,
|
|
level: "never" as SkillLevel, // Valeur par défaut pour une skill nouvellement sélectionnée
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Supprime une skill de l'évaluation
|
|
*/
|
|
async removeSkillFromEvaluation(
|
|
profile: UserProfile,
|
|
category: string,
|
|
skillId: string
|
|
): Promise<void> {
|
|
const pool = getPool();
|
|
const client = await pool.connect();
|
|
|
|
try {
|
|
await client.query("BEGIN");
|
|
|
|
const userId = await userService.upsertUser(profile);
|
|
|
|
// Supprimer directement la skill evaluation
|
|
const deleteQuery = `
|
|
DELETE FROM skill_evaluations
|
|
WHERE user_evaluation_id = (
|
|
SELECT id FROM user_evaluations WHERE user_id = $1
|
|
)
|
|
AND skill_id = $2
|
|
`;
|
|
|
|
await client.query(deleteQuery, [userId, skillId]);
|
|
|
|
await client.query("COMMIT");
|
|
} catch (error) {
|
|
await client.query("ROLLBACK");
|
|
throw error;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Récupère l'évaluation complète de l'utilisateur côté serveur
|
|
* Combine la récupération du cookie et le chargement de l'évaluation
|
|
*/
|
|
async getServerUserEvaluation(userUuid: string) {
|
|
if (!userUuid) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return await this.loadUserEvaluationByUuid(userUuid);
|
|
} catch (error) {
|
|
console.error("Failed to get user evaluation:", error);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Instance singleton
|
|
export const evaluationService = new EvaluationService();
|