refactor: rule of coverage are in one place

This commit is contained in:
Julien Froidefond
2025-08-27 14:31:05 +02:00
parent a5bcdd34fb
commit a8cad0b2ec
16 changed files with 430 additions and 133 deletions

View File

@@ -2,6 +2,10 @@ import { getPool } from "./database";
import { Team, SkillCategory } from "@/lib/types";
import { TeamMember, TeamStats, DirectionStats } from "@/lib/admin-types";
import { SkillsService } from "./skills-service";
import {
COVERAGE_OBJECTIVES,
generateSkillCoverageSQL,
} from "@/lib/evaluation-utils";
export class AdminService {
/**
@@ -9,8 +13,11 @@ export class AdminService {
*/
static async getTeamsStats(): Promise<TeamStats[]> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
// Récupérer toutes les équipes avec leurs membres et évaluations
const query = `
WITH team_members AS (
@@ -56,14 +63,7 @@ export class AdminService {
s.icon as skill_icon,
s.importance,
AVG(ss.level_numeric) as avg_level,
COALESCE(
SUM(CASE
WHEN ss.level_numeric >= 2 THEN 100.0 -- autonomous ou expert
WHEN ss.level_numeric = 1 THEN 50.0 -- not-autonomous
ELSE 0.0 -- never
END) / NULLIF(COUNT(*), 0),
0
) as coverage
${generateSkillCoverageSQL("ss.level_numeric")} as coverage
FROM skill_stats ss
JOIN skills s ON ss.skill_id = s.id
WHERE ss.skill_name IS NOT NULL
@@ -73,11 +73,17 @@ export class AdminService {
SELECT
team_id,
COALESCE(
AVG(CASE WHEN importance = 'incontournable' THEN coverage ELSE NULL END),
AVG(CASE
WHEN importance = 'incontournable' THEN coverage
ELSE NULL
END),
0
) as incontournable_coverage,
COALESCE(
AVG(CASE WHEN importance = 'majeure' THEN coverage ELSE NULL END),
AVG(CASE
WHEN importance = 'majeure' THEN coverage
ELSE NULL
END),
0
) as majeure_coverage
FROM team_skill_averages
@@ -149,7 +155,8 @@ export class AdminService {
ORDER BY tm.direction, tm.team_name
`;
const result = await pool.query(query);
const result = await client.query(query);
await client.query("COMMIT");
return result.rows.map((row) => ({
teamId: row.team_id,
@@ -168,8 +175,11 @@ export class AdminService {
),
}));
} catch (error) {
await client.query("ROLLBACK");
console.error("Error fetching teams stats:", error);
throw new Error("Failed to fetch teams statistics");
} finally {
client.release();
}
}
@@ -241,10 +251,15 @@ export class AdminService {
skills: any[];
}> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
const [categoriesResult, skills] = await Promise.all([
pool.query("SELECT id, name, icon FROM skill_categories ORDER BY name"),
client.query(
"SELECT id, name, icon FROM skill_categories ORDER BY name"
),
SkillsService.getAllSkillsWithUsage(),
]);
@@ -254,13 +269,18 @@ export class AdminService {
skills: [],
}));
await client.query("COMMIT");
return {
skillCategories,
skills,
};
} catch (error) {
await client.query("ROLLBACK");
console.error("Error fetching skills page data:", error);
throw new Error("Failed to fetch skills page data");
} finally {
client.release();
}
}
@@ -308,13 +328,16 @@ export class AdminService {
users: any[];
}> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
const [teamsResult, usersResult] = await Promise.all([
pool.query(
client.query(
"SELECT id, name, direction FROM teams ORDER BY direction, name"
),
pool.query(`
client.query(`
SELECT
u.uuid_id as uuid,
u.first_name as "firstName",
@@ -330,13 +353,18 @@ export class AdminService {
`),
]);
await client.query("COMMIT");
return {
teams: teamsResult.rows,
users: usersResult.rows,
};
} catch (error) {
await client.query("ROLLBACK");
console.error("Error fetching users page data:", error);
throw new Error("Failed to fetch users page data");
} finally {
client.release();
}
}
@@ -349,10 +377,13 @@ export class AdminService {
directionStats: DirectionStats[];
}> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
const [teamsResult, teamStats] = await Promise.all([
pool.query(
client.query(
"SELECT id, name, direction FROM teams ORDER BY direction, name"
),
AdminService.getTeamsStats(),
@@ -360,14 +391,19 @@ export class AdminService {
const directionStats = AdminService.generateDirectionStats(teamStats);
await client.query("COMMIT");
return {
teams: teamsResult.rows,
teamStats,
directionStats,
};
} catch (error) {
await client.query("ROLLBACK");
console.error("Error fetching teams page data:", error);
throw new Error("Failed to fetch teams page data");
} finally {
client.release();
}
}
@@ -381,13 +417,18 @@ export class AdminService {
directionStats: DirectionStats[];
}> {
const pool = getPool();
const client = await pool.connect();
try {
await client.query("BEGIN");
const [teamsResult, categoriesResult, teamStats] = await Promise.all([
pool.query(
client.query(
"SELECT id, name, direction FROM teams ORDER BY direction, name"
),
pool.query("SELECT id, name, icon FROM skill_categories ORDER BY name"),
client.query(
"SELECT id, name, icon FROM skill_categories ORDER BY name"
),
AdminService.getTeamsStats(),
]);
@@ -400,6 +441,8 @@ export class AdminService {
const directionStats = AdminService.generateDirectionStats(teamStats);
await client.query("COMMIT");
return {
teams,
skillCategories,
@@ -407,8 +450,11 @@ export class AdminService {
directionStats,
};
} catch (error) {
await client.query("ROLLBACK");
console.error("Error fetching overview page data:", error);
throw new Error("Failed to fetch overview page data");
} finally {
client.release();
}
}
}