feat: update evaluation schema and refactor skill evaluations

- Added unique constraint on users table for first_name, last_name, and team_id.
- Removed category_evaluations and selected_skills tables, simplifying the schema.
- Updated skill_evaluations to directly reference user_evaluations and include is_selected flag.
- Refactored EvaluationService to handle skill evaluations directly, improving data integrity and performance.
- Introduced new queries for loading skills and evaluations, enhancing data retrieval efficiency.
- Added views for user skills summary and category skills count for easier querying.
This commit is contained in:
Julien Froidefond
2025-08-21 11:41:35 +02:00
parent a749f7fcff
commit 75c3b2c983
2 changed files with 175 additions and 254 deletions

View File

@@ -95,15 +95,15 @@ export class EvaluationService {
const userEvaluationId = userEvalResult.rows[0].id;
// 3. Supprimer les anciennes évaluations de catégories
// 3. Supprimer les anciennes évaluations de skills
await client.query(
"DELETE FROM category_evaluations WHERE user_evaluation_id = $1",
"DELETE FROM skill_evaluations WHERE user_evaluation_id = $1",
[userEvaluationId]
);
// 4. Sauvegarder les nouvelles évaluations
// 4. Sauvegarder les nouvelles évaluations directement
for (const catEval of evaluation.evaluations) {
await this.saveCategoryEvaluation(client, userEvaluationId, catEval);
await this.saveSkillEvaluations(client, userEvaluationId, catEval);
}
await client.query("COMMIT");
@@ -116,33 +116,30 @@ export class EvaluationService {
}
/**
* Sauvegarde une évaluation de catégorie
* Sauvegarde les évaluations de skills directement
*/
private async saveCategoryEvaluation(
private async saveSkillEvaluations(
client: any,
userEvaluationId: number,
catEval: CategoryEvaluation
): Promise<void> {
// Insérer la catégorie d'évaluation
const catEvalQuery = `
INSERT INTO category_evaluations (user_evaluation_id, category)
VALUES ($1, $2)
RETURNING id
`;
const catEvalResult = await client.query(catEvalQuery, [
userEvaluationId,
catEval.category,
]);
const categoryEvaluationId = catEvalResult.rows[0].id;
// Insérer les skills sélectionnées
// Insérer les skills sélectionnées (sans évaluation)
for (const skillId of catEval.selectedSkillIds) {
await client.query(
"INSERT INTO selected_skills (category_evaluation_id, skill_id) VALUES ($1, $2)",
[categoryEvaluationId, skillId]
const isEvaluated = catEval.skills.some(
(se) => se.skillId === skillId && se.level !== null
);
if (!isEvaluated) {
// Skill sélectionnée mais pas encore évaluée
await client.query(
`
INSERT INTO skill_evaluations
(user_evaluation_id, skill_id, level, is_selected, can_mentor, wants_to_learn)
VALUES ($1, $2, 'never'::skill_level_enum, true, false, false)
`,
[userEvaluationId, skillId]
);
}
}
// Insérer les évaluations de skills
@@ -151,11 +148,11 @@ export class EvaluationService {
await client.query(
`
INSERT INTO skill_evaluations
(category_evaluation_id, skill_id, level, can_mentor, wants_to_learn)
VALUES ($1, $2, $3, $4, $5)
(user_evaluation_id, skill_id, level, is_selected, can_mentor, wants_to_learn)
VALUES ($1, $2, $3, true, $4, $5)
`,
[
categoryEvaluationId,
userEvaluationId,
skillEval.skillId,
skillEval.level,
skillEval.canMentor || false,
@@ -200,38 +197,58 @@ export class EvaluationService {
const userData = userResult.rows[0];
const userEvaluationId = userData.user_evaluation_id;
// Charger les évaluations de catégories
const categoriesQuery = `
SELECT ce.*,
array_agg(DISTINCT ss.skill_id) FILTER (WHERE ss.skill_id IS NOT NULL) as selected_skill_ids,
array_agg(
json_build_object(
'skillId', se.skill_id,
'level', se.level,
'canMentor', se.can_mentor,
'wantsToLearn', se.wants_to_learn
)
) FILTER (WHERE se.skill_id IS NOT NULL) as skill_evaluations
FROM category_evaluations ce
LEFT JOIN selected_skills ss ON ce.id = ss.category_evaluation_id
LEFT JOIN skill_evaluations se ON ce.id = se.category_evaluation_id
WHERE ce.user_evaluation_id = $1
GROUP BY ce.id, ce.category
ORDER BY ce.category
// 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 categoriesResult = await client.query(categoriesQuery, [
userEvaluationId,
]);
const skillsResult = await client.query(skillsQuery, [userEvaluationId]);
const evaluations: CategoryEvaluation[] = categoriesResult.rows.map(
(row) => ({
category: row.category,
selectedSkillIds: row.selected_skill_ids || [],
skills: (row.skill_evaluations || []).filter(
(se: any) => se.skillId !== null
),
})
// 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 {
@@ -253,60 +270,7 @@ export class EvaluationService {
skillId: string,
level: SkillLevel
): Promise<void> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
const userId = await this.upsertUser(profile);
// Obtenir ou créer 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;
// Obtenir ou créer category_evaluation
const catEvalResult = await client.query(
`
INSERT INTO category_evaluations (user_evaluation_id, category)
VALUES ($1, $2)
ON CONFLICT (user_evaluation_id, category)
DO UPDATE SET category = $2
RETURNING id
`,
[userEvaluationId, category]
);
const categoryEvaluationId = catEvalResult.rows[0].id;
// Upsert skill evaluation
await client.query(
`
INSERT INTO skill_evaluations (category_evaluation_id, skill_id, level)
VALUES ($1, $2, $3)
ON CONFLICT (category_evaluation_id, skill_id)
DO UPDATE SET level = $3, updated_at = CURRENT_TIMESTAMP
`,
[categoryEvaluationId, skillId, level]
);
await client.query("COMMIT");
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
await this.upsertSkillEvaluation(profile, skillId, { level });
}
/**
@@ -318,13 +282,7 @@ export class EvaluationService {
skillId: string,
canMentor: boolean
): Promise<void> {
await this.updateSkillProperty(
profile,
category,
skillId,
"can_mentor",
canMentor
);
await this.upsertSkillEvaluation(profile, skillId, { canMentor });
}
/**
@@ -336,24 +294,21 @@ export class EvaluationService {
skillId: string,
wantsToLearn: boolean
): Promise<void> {
await this.updateSkillProperty(
profile,
category,
skillId,
"wants_to_learn",
wantsToLearn
);
await this.upsertSkillEvaluation(profile, skillId, { wantsToLearn });
}
/**
* Méthode utilitaire pour mettre à jour une propriété de skill
* Méthode utilitaire pour mettre à jour ou créer une évaluation de skill
*/
private async updateSkillProperty(
private async upsertSkillEvaluation(
profile: UserProfile,
category: string,
skillId: string,
property: "can_mentor" | "wants_to_learn",
value: boolean
updates: {
level?: SkillLevel;
canMentor?: boolean;
wantsToLearn?: boolean;
isSelected?: boolean;
}
): Promise<void> {
const pool = getPool();
const client = await pool.connect();
@@ -363,6 +318,7 @@ export class EvaluationService {
const userId = await this.upsertUser(profile);
// Upsert user_evaluation
const userEvalResult = await client.query(
`
INSERT INTO user_evaluations (user_id, last_updated)
@@ -376,27 +332,39 @@ export class EvaluationService {
const userEvaluationId = userEvalResult.rows[0].id;
const catEvalResult = await client.query(
`
INSERT INTO category_evaluations (user_evaluation_id, category)
VALUES ($1, $2)
ON CONFLICT (user_evaluation_id, category)
DO UPDATE SET category = $2
RETURNING id
`,
[userEvaluationId, category]
);
const categoryEvaluationId = catEvalResult.rows[0].id;
// Update the specific property
const updateQuery = `
UPDATE skill_evaluations
SET ${property} = $3, updated_at = CURRENT_TIMESTAMP
WHERE category_evaluation_id = $1 AND skill_id = $2
// 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(updateQuery, [categoryEvaluationId, skillId, value]);
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) {
@@ -415,57 +383,10 @@ export class EvaluationService {
category: string,
skillId: string
): Promise<void> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
const userId = await this.upsertUser(profile);
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;
const catEvalResult = await client.query(
`
INSERT INTO category_evaluations (user_evaluation_id, category)
VALUES ($1, $2)
ON CONFLICT (user_evaluation_id, category)
DO UPDATE SET category = $2
RETURNING id
`,
[userEvaluationId, category]
);
const categoryEvaluationId = catEvalResult.rows[0].id;
// Ajouter à selected_skills
await client.query(
`
INSERT INTO selected_skills (category_evaluation_id, skill_id)
VALUES ($1, $2)
ON CONFLICT DO NOTHING
`,
[categoryEvaluationId, skillId]
);
await client.query("COMMIT");
} catch (error) {
await client.query("ROLLBACK");
throw error;
} finally {
client.release();
}
await this.upsertSkillEvaluation(profile, skillId, {
isSelected: true,
level: "never" as SkillLevel, // Valeur par défaut pour une skill nouvellement sélectionnée
});
}
/**
@@ -482,36 +403,18 @@ export class EvaluationService {
try {
await client.query("BEGIN");
// Trouver les IDs nécessaires
const findQuery = `
SELECT ce.id as category_evaluation_id
FROM users u
JOIN user_evaluations ue ON u.id = ue.user_id
JOIN category_evaluations ce ON ue.id = ce.user_evaluation_id
WHERE u.first_name = $1 AND u.last_name = $2 AND u.team_id = $3 AND ce.category = $4
const userId = await this.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
`;
const result = await client.query(findQuery, [
profile.firstName,
profile.lastName,
profile.teamId,
category,
]);
if (result.rows.length > 0) {
const categoryEvaluationId = result.rows[0].category_evaluation_id;
// Supprimer de selected_skills et skill_evaluations
await client.query(
"DELETE FROM selected_skills WHERE category_evaluation_id = $1 AND skill_id = $2",
[categoryEvaluationId, skillId]
);
await client.query(
"DELETE FROM skill_evaluations WHERE category_evaluation_id = $1 AND skill_id = $2",
[categoryEvaluationId, skillId]
);
}
await client.query(deleteQuery, [userId, skillId]);
await client.query("COMMIT");
} catch (error) {