1222 lines
54 KiB
JavaScript
1222 lines
54 KiB
JavaScript
const express = require('express');
|
||
const sql = require('mssql');
|
||
const cors = require('cors');
|
||
|
||
const app = express();
|
||
app.use(cors());
|
||
app.use(express.json());
|
||
|
||
const sqlConfig = {
|
||
user: 'RG-Competences',
|
||
password: 'P@ssw0rd2026!',
|
||
server: '192.168.0.3',
|
||
database: 'RG-Competences',
|
||
options: {
|
||
encrypt: true,
|
||
trustServerCertificate: true,
|
||
enableArithAbort: true
|
||
}
|
||
};
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// UTILITAIRE : LOG HISTORIQUE (NOUVELLE VERSION - UNE LIGNE PAR ACTION)
|
||
// ═══════════════════════════════════════════════════════
|
||
async function logHistoryComplete(data) {
|
||
try {
|
||
const pool = await sql.connect(sqlConfig);
|
||
await pool.request()
|
||
.input('record_id', sql.NVarChar, data.record_id)
|
||
.input('action_type', sql.NVarChar, data.action_type)
|
||
.input('action_description', sql.NVarChar, data.action_description)
|
||
.input('author_email', sql.NVarChar, data.author_email)
|
||
.input('author_name', sql.NVarChar, data.author_name)
|
||
.input('author_role', sql.NVarChar, data.author_role)
|
||
.input('campus', sql.NVarChar, data.campus || null)
|
||
.input('ip_address', sql.NVarChar, data.ip_address || null)
|
||
// Nouvelles colonnes
|
||
.input('auto_evaluation_old', sql.NVarChar, data.auto_evaluation_old || null)
|
||
.input('auto_evaluation_new', sql.NVarChar, data.auto_evaluation_new || null)
|
||
.input('evaluation_tuteur_old', sql.NVarChar, data.evaluation_tuteur_old || null)
|
||
.input('evaluation_tuteur_new', sql.NVarChar, data.evaluation_tuteur_new || null)
|
||
.input('date_action_old', sql.Date, data.date_action_old || null)
|
||
.input('date_action_new', sql.Date, data.date_action_new || null)
|
||
.input('date_action_rre_old', sql.Date, data.date_action_rre_old || null)
|
||
.input('date_action_rre_new', sql.Date, data.date_action_rre_new || null)
|
||
.input('date_eval_tuteur_old', sql.Date, data.date_eval_tuteur_old || null)
|
||
.input('date_eval_tuteur_new', sql.Date, data.date_eval_tuteur_new || null)
|
||
.input('commentaire_formateur_old', sql.NVarChar, data.commentaire_formateur_old || null)
|
||
.input('commentaire_formateur_new', sql.NVarChar, data.commentaire_formateur_new || null)
|
||
.input('commentaire_rre_old', sql.NVarChar, data.commentaire_rre_old || null)
|
||
.input('commentaire_rre_new', sql.NVarChar, data.commentaire_rre_new || null)
|
||
.input('commentaire_old', sql.NVarChar, data.commentaire_old || null)
|
||
.input('commentaire_new', sql.NVarChar, data.commentaire_new || null)
|
||
.query(`
|
||
INSERT INTO [dbo].[tbl_Historique_Evaluations]
|
||
(record_id, action_type, action_description,
|
||
author_email, author_name, author_role, campus, ip_address,
|
||
auto_evaluation_old, auto_evaluation_new,
|
||
evaluation_tuteur_old, evaluation_tuteur_new,
|
||
date_action_old, date_action_new,
|
||
date_action_rre_old, date_action_rre_new,
|
||
date_eval_tuteur_old, date_eval_tuteur_new,
|
||
commentaire_formateur_old, commentaire_formateur_new,
|
||
commentaire_rre_old, commentaire_rre_new,
|
||
commentaire_old, commentaire_new)
|
||
VALUES
|
||
(@record_id, @action_type, @action_description,
|
||
@author_email, @author_name, @author_role, @campus, @ip_address,
|
||
@auto_evaluation_old, @auto_evaluation_new,
|
||
@evaluation_tuteur_old, @evaluation_tuteur_new,
|
||
@date_action_old, @date_action_new,
|
||
@date_action_rre_old, @date_action_rre_new,
|
||
@date_eval_tuteur_old, @date_eval_tuteur_new,
|
||
@commentaire_formateur_old, @commentaire_formateur_new,
|
||
@commentaire_rre_old, @commentaire_rre_new,
|
||
@commentaire_old, @commentaire_new)
|
||
`);
|
||
console.log('✅ Historique complet enregistré:', data.action_type);
|
||
} catch (error) {
|
||
console.error('❌ Erreur log historique:', error);
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// AUTHENTIFICATION
|
||
// ═══════════════════════════════════════════════════════
|
||
async function getUserWithPermissions(userPrincipalName) {
|
||
try {
|
||
const pool = await sql.connect(sqlConfig);
|
||
|
||
const superUserResult = await pool.request()
|
||
.input('userPrincipalName', sql.NVarChar, userPrincipalName)
|
||
.query(`
|
||
SELECT TOP 1
|
||
userPrincipalName,
|
||
nom + ' ' + prenom as nom_complet,
|
||
nom,
|
||
prenom,
|
||
campus,
|
||
COALESCE(role, 'super') as role
|
||
FROM [dbo].[tbl_SuperUtilisateur]
|
||
WHERE userPrincipalName = @userPrincipalName
|
||
AND actif = 1
|
||
`);
|
||
|
||
if (superUserResult.recordset.length > 0) {
|
||
const superUser = superUserResult.recordset[0];
|
||
const userRole = superUser.role || 'super';
|
||
|
||
// Définir les permissions selon le rôle
|
||
let permissions = {};
|
||
|
||
if (userRole === 'lecteur') {
|
||
// Lecteur : peut voir son campus mais rien modifier
|
||
permissions = {
|
||
peut_valider_evaluations: false,
|
||
peut_gerer_formateurs: false,
|
||
peut_saisir_evaluations: false,
|
||
peut_voir_tous_campus: false, // ✅ Limité à son campus
|
||
peut_exporter_donnees: true, // Peut exporter
|
||
peut_usurper_identite: false,
|
||
peut_modifier: false // ✅ Ne peut pas modifier
|
||
};
|
||
} else {
|
||
// Super : peut modifier son campus uniquement
|
||
permissions = {
|
||
peut_valider_evaluations: true,
|
||
peut_gerer_formateurs: true,
|
||
peut_saisir_evaluations: true,
|
||
peut_voir_tous_campus: false, // ✅ Limité à son campus
|
||
peut_exporter_donnees: true,
|
||
peut_usurper_identite: true,
|
||
peut_modifier: true // ✅ Peut modifier
|
||
};
|
||
}
|
||
|
||
return {
|
||
id: superUser.userPrincipalName,
|
||
email: superUser.userPrincipalName,
|
||
name: superUser.prenom || superUser.nom,
|
||
nomComplet: superUser.nom_complet,
|
||
campus: superUser.campus,
|
||
role: userRole,
|
||
permissions: permissions
|
||
};
|
||
}
|
||
|
||
const userResult = await pool.request()
|
||
.input('userPrincipalName', sql.NVarChar, userPrincipalName)
|
||
.query(`
|
||
SELECT TOP 1
|
||
userPrincipalName,
|
||
nom_dip as nom_complet,
|
||
Nom_brut as nom,
|
||
givenname as prenom,
|
||
Campus,
|
||
'formateur' as role
|
||
FROM [dbo].[v_R_Promo_Formateur_Referent]
|
||
WHERE userPrincipalName = @userPrincipalName
|
||
|
||
UNION ALL
|
||
|
||
SELECT TOP 1
|
||
userPrincipalName,
|
||
nom + ' ' + prenom as nom_complet,
|
||
nom,
|
||
prenom,
|
||
campus as Campus,
|
||
'rre' as role
|
||
FROM [dbo].[tbl_Responsable_RRE]
|
||
WHERE userPrincipalName = @userPrincipalName
|
||
AND actif = 1
|
||
`);
|
||
|
||
if (userResult.recordset.length === 0) return null;
|
||
|
||
const user = userResult.recordset[0];
|
||
const role = user.role || 'rre';
|
||
|
||
return {
|
||
id: user.userPrincipalName,
|
||
email: user.userPrincipalName,
|
||
name: user.prenom || user.nom,
|
||
nomComplet: user.nom_complet,
|
||
campus: user.Campus,
|
||
role: role,
|
||
permissions: {
|
||
peut_valider_evaluations: role === 'rre',
|
||
peut_gerer_formateurs: role === 'rre',
|
||
peut_saisir_evaluations: true,
|
||
peut_voir_tous_campus: role === 'rre',
|
||
peut_exporter_donnees: role === 'rre',
|
||
peut_modifier: true
|
||
}
|
||
};
|
||
} catch (error) {
|
||
console.error('❌ getUserWithPermissions:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// SUPER-UTILISATEUR : LISTE DES FORMATEURS ET RRE
|
||
// ═══════════════════════════════════════════════════════
|
||
app.get('/api/super/users', async (req, res) => {
|
||
try {
|
||
const userRole = req.headers['x-user-role'];
|
||
|
||
if (userRole !== 'super') {
|
||
return res.status(403).json({ error: 'Accès réservé aux super-utilisateurs' });
|
||
}
|
||
|
||
const pool = await sql.connect(sqlConfig);
|
||
|
||
const formateurs = await pool.request().query(`
|
||
SELECT DISTINCT
|
||
userPrincipalName as id,
|
||
nom_dip as nom_complet,
|
||
Nom_brut as nom,
|
||
givenname as prenom,
|
||
Campus as campus,
|
||
'formateur' as role
|
||
FROM [dbo].[v_R_Promo_Formateur_Referent]
|
||
WHERE userPrincipalName IS NOT NULL
|
||
ORDER BY nom_dip ASC
|
||
`);
|
||
|
||
const rres = await pool.request().query(`
|
||
SELECT
|
||
userPrincipalName as id,
|
||
nom + ' ' + prenom as nom_complet,
|
||
nom,
|
||
prenom,
|
||
campus,
|
||
'rre' as role
|
||
FROM [dbo].[tbl_Responsable_RRE]
|
||
WHERE actif = 1
|
||
ORDER BY nom ASC, prenom ASC
|
||
`);
|
||
|
||
const allUsers = [
|
||
...formateurs.recordset.map(f => ({
|
||
id: f.id,
|
||
name: f.nom_complet,
|
||
email: f.id,
|
||
campus: f.campus,
|
||
role: 'formateur'
|
||
})),
|
||
...rres.recordset.map(r => ({
|
||
id: r.id,
|
||
name: r.nom_complet,
|
||
email: r.id,
|
||
campus: r.campus,
|
||
role: 'rre'
|
||
}))
|
||
];
|
||
|
||
res.json(allUsers);
|
||
console.log('✅ Liste des utilisateurs pour super-user:', allUsers.length);
|
||
} catch (error) {
|
||
console.error('❌ Erreur /api/super/users:', error);
|
||
res.status(500).json({ error: 'Erreur serveur' });
|
||
}
|
||
});
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// SUPER-UTILISATEUR : USURPER UNE IDENTITÉ
|
||
// ═══════════════════════════════════════════════════════
|
||
app.post('/api/super/impersonate', async (req, res) => {
|
||
try {
|
||
const userRole = req.headers['x-user-role'];
|
||
const { targetEmail } = req.body;
|
||
|
||
if (userRole !== 'super') {
|
||
return res.status(403).json({ error: 'Accès réservé aux super-utilisateurs' });
|
||
}
|
||
|
||
if (!targetEmail) {
|
||
return res.status(400).json({ error: 'Email cible requis' });
|
||
}
|
||
|
||
const targetUser = await getUserWithPermissions(targetEmail);
|
||
|
||
if (!targetUser || targetUser.role === 'super' || targetUser.role === 'lecteur') {
|
||
return res.status(404).json({ error: 'Utilisateur cible non trouvé ou invalide' });
|
||
}
|
||
|
||
console.log(`🎭 Super-user usurpe l'identité de: ${targetUser.email} (${targetUser.role})`);
|
||
|
||
res.json({
|
||
success: true,
|
||
impersonatedUser: targetUser
|
||
});
|
||
} catch (error) {
|
||
console.error('❌ Erreur impersonate:', error);
|
||
res.status(500).json({ error: 'Erreur serveur' });
|
||
}
|
||
});
|
||
|
||
app.post('/auth/verify', async (req, res) => {
|
||
const { email } = req.body;
|
||
console.log('🔍 Vérification:', email);
|
||
try {
|
||
const user = await getUserWithPermissions(email);
|
||
if (user) {
|
||
console.log('✅ User:', user.role, user.campus);
|
||
res.json({ valid: true, user });
|
||
} else {
|
||
console.log('❌ Non trouvé');
|
||
res.status(403).json({ valid: false });
|
||
}
|
||
} catch (error) {
|
||
console.error('❌ Erreur auth:', error);
|
||
res.status(500).json({ error: 'Erreur serveur' });
|
||
}
|
||
});
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// UTILITAIRE : OBTENIR L'UTILISATEUR EFFECTIF
|
||
// ═══════════════════════════════════════════════════════
|
||
function getEffectiveUser(headers) {
|
||
const realRole = headers['x-user-role'];
|
||
const impersonatedEmail = headers['x-impersonated-email'];
|
||
const impersonatedRole = headers['x-impersonated-role'];
|
||
const impersonatedCampus = headers['x-impersonated-campus'];
|
||
|
||
if (realRole === 'super' && impersonatedEmail) {
|
||
return {
|
||
email: impersonatedEmail,
|
||
role: impersonatedRole,
|
||
campus: impersonatedCampus,
|
||
isImpersonating: true,
|
||
isSuperUser: true
|
||
};
|
||
}
|
||
|
||
return {
|
||
email: headers['x-user-email'],
|
||
role: headers['x-user-role'],
|
||
campus: headers['x-user-campus'],
|
||
isImpersonating: false,
|
||
isSuperUser: realRole === 'super',
|
||
isLecteur: realRole === 'lecteur'
|
||
};
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// FILTRES - CAMPUS
|
||
// ═══════════════════════════════════════════════════════
|
||
app.get('/api/campus-list', async (req, res) => {
|
||
try {
|
||
const effectiveUser = getEffectiveUser(req.headers);
|
||
const { email, role, campus, isSuperUser, isLecteur } = effectiveUser;
|
||
|
||
const pool = await sql.connect(sqlConfig);
|
||
let result;
|
||
|
||
// 🔥 SUPER OU LECTEUR SANS USURPATION : SON campus uniquement
|
||
if ((isSuperUser || isLecteur) && !effectiveUser.isImpersonating) {
|
||
if (campus) {
|
||
result = await pool.request()
|
||
.input('userCampus', sql.NVarChar, campus)
|
||
.query(`
|
||
SELECT DISTINCT Campus
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE Campus = @userCampus
|
||
ORDER BY Campus ASC
|
||
`);
|
||
} else {
|
||
// Si pas de campus défini, tous les campus (fallback)
|
||
result = await pool.request().query(`
|
||
SELECT DISTINCT Campus
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE Campus IS NOT NULL AND Campus != ''
|
||
ORDER BY Campus ASC
|
||
`);
|
||
}
|
||
}
|
||
// Formateur : ses campus uniquement
|
||
else if (role === 'formateur' && email) {
|
||
result = await pool.request()
|
||
.input('userEmail', sql.NVarChar, email)
|
||
.query(`
|
||
SELECT DISTINCT Campus
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE Campus IS NOT NULL AND Campus != ''
|
||
AND userPrincipalName = @userEmail
|
||
ORDER BY Campus ASC
|
||
`);
|
||
}
|
||
// RRE : son campus uniquement
|
||
else if (role === 'rre' && campus) {
|
||
result = await pool.request()
|
||
.input('userCampus', sql.NVarChar, campus)
|
||
.query(`
|
||
SELECT DISTINCT Campus
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE Campus = @userCampus
|
||
ORDER BY Campus ASC
|
||
`);
|
||
}
|
||
// Fallback : tous les campus
|
||
else {
|
||
result = await pool.request().query(`
|
||
SELECT DISTINCT Campus
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE Campus IS NOT NULL AND Campus != ''
|
||
ORDER BY Campus ASC
|
||
`);
|
||
}
|
||
|
||
res.json(result.recordset.map(r => r.Campus));
|
||
console.log(`📍 Campus (${role}${isSuperUser ? ' [SUPER]' : ''}${isLecteur ? ' [LECTEUR]' : ''}${effectiveUser.isImpersonating ? ' [USURPÉ]' : ''}):`, result.recordset.length);
|
||
} catch (error) {
|
||
console.error('❌ campus-list:', error);
|
||
res.status(500).json({ error: 'Erreur campus' });
|
||
}
|
||
});
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// FILTRES - PROMOTIONS
|
||
// ═══════════════════════════════════════════════════════
|
||
app.get('/api/promo-list', async (req, res) => {
|
||
try {
|
||
const { campus } = req.query;
|
||
const effectiveUser = getEffectiveUser(req.headers);
|
||
const { email, role, campus: userCampus, isSuperUser, isLecteur } = effectiveUser;
|
||
|
||
const pool = await sql.connect(sqlConfig);
|
||
const request = pool.request();
|
||
|
||
let query = `
|
||
SELECT DISTINCT NOM_DIP as promo
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE NOM_DIP IS NOT NULL AND NOM_DIP != ''
|
||
`;
|
||
|
||
// 🔥 SUPER OU LECTEUR SANS USURPATION : filtrer par SON campus
|
||
if ((isSuperUser || isLecteur) && !effectiveUser.isImpersonating) {
|
||
if (userCampus) {
|
||
query += ` AND Campus = @userCampus`;
|
||
request.input('userCampus', sql.NVarChar, userCampus);
|
||
}
|
||
}
|
||
// Formateur : filtrer par son email
|
||
else if (role === 'formateur' && email) {
|
||
query += ` AND userPrincipalName = @userEmail`;
|
||
request.input('userEmail', sql.NVarChar, email);
|
||
}
|
||
// RRE : filtrer par son campus
|
||
else if (role === 'rre' && userCampus) {
|
||
query += ` AND Campus = @userCampus`;
|
||
request.input('userCampus', sql.NVarChar, userCampus);
|
||
}
|
||
|
||
// Filtre par campus sélectionné
|
||
if (campus && campus !== 'tous') {
|
||
query += ` AND Campus = @campus`;
|
||
request.input('campus', sql.NVarChar, campus);
|
||
}
|
||
|
||
query += ` ORDER BY NOM_DIP ASC`;
|
||
|
||
const result = await request.query(query);
|
||
res.json(result.recordset.map(r => r.promo));
|
||
console.log(`🎓 Promotions (${role}${isSuperUser ? ' [SUPER]' : ''}${isLecteur ? ' [LECTEUR]' : ''}):`, result.recordset.length);
|
||
} catch (error) {
|
||
console.error('❌ promo-list:', error);
|
||
res.status(500).json({ error: 'Erreur promotions' });
|
||
}
|
||
});
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// FILTRES - ÉTUDIANTS
|
||
// ═══════════════════════════════════════════════════════
|
||
app.get("/api/student-list", async (req, res) => {
|
||
try {
|
||
const { campus, promo } = req.query;
|
||
|
||
const effectiveUser = getEffectiveUser(req.headers);
|
||
const { email, role, campus: userCampus, isSuperUser, isLecteur } = effectiveUser;
|
||
|
||
const pool = await sql.connect(sqlConfig);
|
||
const request = pool.request();
|
||
|
||
let query = `
|
||
SELECT
|
||
LOGin as id,
|
||
PRENOM + ' ' + NOM as name,
|
||
Campus,
|
||
NOM_DIP as formation
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE LOGin IS NOT NULL
|
||
AND LOGin <> ''
|
||
`;
|
||
|
||
const conditions = [];
|
||
|
||
// 🔥 SUPER OU LECTEUR SANS USURPATION : filtrer par SON campus
|
||
if ((isSuperUser || isLecteur) && !effectiveUser.isImpersonating) {
|
||
if (userCampus) {
|
||
conditions.push("Campus = @userCampus");
|
||
request.input("userCampus", sql.NVarChar, userCampus);
|
||
}
|
||
}
|
||
// Formateur : uniquement SES étudiants
|
||
else if (role === "formateur" && email) {
|
||
conditions.push("userPrincipalName = @userEmail");
|
||
request.input("userEmail", sql.NVarChar, email);
|
||
}
|
||
// RRE : uniquement étudiants de SON campus
|
||
else if (role === "rre" && userCampus) {
|
||
conditions.push("Campus = @userCampus");
|
||
request.input("userCampus", sql.NVarChar, userCampus);
|
||
}
|
||
|
||
// Filtre par campus sélectionné
|
||
if (campus && campus !== "tous") {
|
||
conditions.push("Campus = @campus");
|
||
request.input("campus", sql.NVarChar, campus);
|
||
}
|
||
|
||
// Filtre par promo sélectionnée
|
||
if (promo && promo !== "tous") {
|
||
conditions.push("NOM_DIP = @promo");
|
||
request.input("promo", sql.NVarChar, promo);
|
||
}
|
||
|
||
if (conditions.length > 0) {
|
||
query += " AND " + conditions.join(" AND ");
|
||
}
|
||
|
||
query += " ORDER BY NOM ASC, PRENOM ASC";
|
||
|
||
const result = await request.query(query);
|
||
|
||
res.json(result.recordset);
|
||
console.log(
|
||
`👥 Étudiants (${role}${isSuperUser ? ' [SUPER]' : ''}${isLecteur ? ' [LECTEUR]' : ''}${effectiveUser.isImpersonating ? ' [USURPÉ]' : ''}):`,
|
||
result.recordset.length
|
||
);
|
||
} catch (error) {
|
||
console.error("❌ student-list, error:", error);
|
||
res.status(500).json({ error: "Erreur étudiants" });
|
||
}
|
||
});
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// DONNÉES PROMO
|
||
// ═══════════════════════════════════════════════════════
|
||
app.get('/api/promo-data', async (req, res) => {
|
||
try {
|
||
const userRole = req.headers['x-user-role'];
|
||
const userCampus = req.headers['x-user-campus'];
|
||
const userEmail = req.headers['x-user-email'];
|
||
|
||
const pool = await sql.connect(sqlConfig);
|
||
const request = pool.request();
|
||
|
||
let query = `
|
||
SELECT
|
||
LOGin,
|
||
NOM,
|
||
PRENOM,
|
||
Campus,
|
||
NOM_DIP,
|
||
Annee,
|
||
userPrincipalName as formateurEmail
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE LOGin IS NOT NULL
|
||
`;
|
||
|
||
if (userRole === 'formateur' && userEmail) {
|
||
query += ` AND userPrincipalName = @userEmail`;
|
||
request.input('userEmail', sql.NVarChar, userEmail);
|
||
} else if ((userRole === 'rre' || userRole === 'super' || userRole === 'lecteur') && userCampus) {
|
||
query += ` AND Campus = @userCampus`;
|
||
request.input('userCampus', sql.NVarChar, userCampus);
|
||
}
|
||
|
||
query += ` ORDER BY Campus ASC, NOM_DIP ASC, NOM ASC, PRENOM ASC`;
|
||
|
||
const result = await request.query(query);
|
||
res.json(result.recordset);
|
||
console.log(`✅ promo-data (${userRole}):`, result.recordset.length);
|
||
} catch (error) {
|
||
console.error('❌ promo-data:', error);
|
||
res.status(500).json({ error: 'Erreur promo-data' });
|
||
}
|
||
});
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// RÉCUPÉRER HISTORIQUE (NOUVELLE VERSION)
|
||
// ═══════════════════════════════════════════════════════
|
||
app.get('/api/history/:studentId', async (req, res) => {
|
||
try {
|
||
const { studentId } = req.params;
|
||
const pool = await sql.connect(sqlConfig);
|
||
|
||
const result = await pool.request()
|
||
.input('studentId', sql.NVarChar, studentId)
|
||
.query(`
|
||
SELECT
|
||
id,
|
||
action_type,
|
||
action_description,
|
||
author_name,
|
||
author_email,
|
||
author_role,
|
||
campus,
|
||
created_at,
|
||
-- Nouvelles colonnes
|
||
auto_evaluation_old,
|
||
auto_evaluation_new,
|
||
evaluation_tuteur_old,
|
||
evaluation_tuteur_new,
|
||
date_action_old,
|
||
date_action_new,
|
||
date_action_rre_old,
|
||
date_action_rre_new,
|
||
date_eval_tuteur_old,
|
||
date_eval_tuteur_new,
|
||
commentaire_formateur_old,
|
||
commentaire_formateur_new,
|
||
commentaire_rre_old,
|
||
commentaire_rre_new,
|
||
commentaire_old,
|
||
commentaire_new
|
||
FROM [dbo].[tbl_Historique_Evaluations]
|
||
WHERE record_id = @studentId
|
||
ORDER BY created_at DESC
|
||
`);
|
||
|
||
res.json(result.recordset);
|
||
console.log(`📜 Historique de ${studentId}:`, result.recordset.length);
|
||
} catch (error) {
|
||
console.error('❌ Erreur historique:', error);
|
||
res.status(500).json({ error: 'Erreur historique' });
|
||
}
|
||
});
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// RÉCUPÉRER TOUTES LES ÉVALUATIONS
|
||
// ═══════════════════════════════════════════════════════
|
||
app.get('/api/evaluations', async (req, res) => {
|
||
try {
|
||
const effectiveUser = getEffectiveUser(req.headers);
|
||
const { email, role, campus: userCampus, isSuperUser, isLecteur } = effectiveUser;
|
||
const { campus, promo, student, status, sortBy, sortOrder } = req.query;
|
||
|
||
const pool = await sql.connect(sqlConfig);
|
||
const request = pool.request();
|
||
|
||
let query = `
|
||
SELECT
|
||
e.LOGin as id,
|
||
e.NOM as nom,
|
||
e.PRENOM as prenom,
|
||
e.Campus as campus,
|
||
e.NOM_DIP as formation,
|
||
e.NOM_DIP as nomDip,
|
||
e.Annee as annee,
|
||
e.userPrincipalName as formateurEmail,
|
||
COALESCE(ev.auto_evaluation, 'NON') as autoEvaluation,
|
||
COALESCE(ev.evaluation_tuteur, 'NON') as evaluationTuteur,
|
||
ev.date_action as dateAction,
|
||
ev.date_action_rre as dateActionRRE,
|
||
ev.commentaire_formateur as commentaireFormateur,
|
||
ev.date_eval_tuteur as dateEvalTuteur,
|
||
ev.commentaire_rre as commentaireRRE,
|
||
ev.commentaire,
|
||
ev.updated_at as lastUpdate
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant] e
|
||
LEFT JOIN [dbo].[tbl_Evaluations] ev ON e.LOGin = ev.student_id
|
||
WHERE e.LOGin IS NOT NULL
|
||
`;
|
||
|
||
const conditions = [];
|
||
|
||
// 🔥 SUPER OU LECTEUR SANS USURPATION : filtrer par SON campus
|
||
if ((isSuperUser || isLecteur) && !effectiveUser.isImpersonating) {
|
||
if (userCampus) {
|
||
conditions.push('e.Campus = @userCampus');
|
||
request.input('userCampus', sql.NVarChar, userCampus);
|
||
console.log(`🔐 ${role.toUpperCase()}: accès limité au campus`, userCampus);
|
||
} else {
|
||
console.log(`⚠️ ${role.toUpperCase()}: pas de campus défini, accès à tout`);
|
||
}
|
||
}
|
||
// Filtrage par rôle usurpé ou réel
|
||
else if (role === 'formateur' && email) {
|
||
conditions.push('e.userPrincipalName = @userEmail');
|
||
request.input('userEmail', sql.NVarChar, email);
|
||
} else if (role === 'rre' && userCampus) {
|
||
conditions.push('e.Campus = @userCampus');
|
||
request.input('userCampus', sql.NVarChar, userCampus);
|
||
}
|
||
|
||
// Autres filtres (campus, promo, student sélectionnés dans l'interface)
|
||
if (campus && campus !== 'tous') {
|
||
conditions.push('e.Campus = @campus');
|
||
request.input('campus', sql.NVarChar, campus);
|
||
}
|
||
|
||
if (promo && promo !== 'tous') {
|
||
conditions.push('e.NOM_DIP = @promo');
|
||
request.input('promo', sql.NVarChar, promo);
|
||
}
|
||
|
||
if (student && student !== 'tous') {
|
||
conditions.push('e.LOGin = @student');
|
||
request.input('student', sql.NVarChar, student);
|
||
}
|
||
|
||
if (conditions.length > 0) {
|
||
query += ' AND ' + conditions.join(' AND ');
|
||
}
|
||
|
||
// Filtre par statut
|
||
if (status && status !== 'tous') {
|
||
if (status === 'OUI') {
|
||
query += ` AND (COALESCE(ev.auto_evaluation, 'NON') = 'OUI' AND COALESCE(ev.evaluation_tuteur, 'NON') = 'OUI')`;
|
||
} else if (status === 'PARTIEL') {
|
||
query += ` AND (COALESCE(ev.auto_evaluation, 'NON') = 'PARTIEL' OR COALESCE(ev.evaluation_tuteur, 'NON') = 'PARTIEL')`;
|
||
} else if (status === 'NON') {
|
||
query += ` AND (COALESCE(ev.auto_evaluation, 'NON') = 'NON' OR COALESCE(ev.evaluation_tuteur, 'NON') = 'NON')`;
|
||
}
|
||
}
|
||
|
||
// Tri
|
||
const validSortColumns = {
|
||
'nom': 'e.NOM, e.PRENOM',
|
||
'campus': 'e.Campus',
|
||
'promo': 'e.NOM_DIP',
|
||
'formation': 'e.NOM_DIP',
|
||
'date': 'ev.updated_at'
|
||
};
|
||
|
||
const orderColumn = validSortColumns[sortBy] || 'e.NOM, e.PRENOM';
|
||
const orderDirection = sortOrder === 'desc' ? 'DESC' : 'ASC';
|
||
|
||
query += ` ORDER BY ${orderColumn} ${orderDirection}`;
|
||
|
||
const result = await request.query(query);
|
||
|
||
const records = result.recordset.map(record => ({
|
||
...record,
|
||
email: `${record.id}@example.com`,
|
||
campusPromo: record.campus,
|
||
history: []
|
||
}));
|
||
|
||
res.json({
|
||
data: records
|
||
});
|
||
|
||
console.log(`✅ Évaluations (${role}${isSuperUser ? ' [SUPER]' : ''}${isLecteur ? ' [LECTEUR]' : ''}${effectiveUser.isImpersonating ? ' [USURPÉ]' : ''}):`, records.length, 'étudiants');
|
||
} catch (error) {
|
||
console.error('❌ Erreur évaluations:', error);
|
||
res.status(500).json({ error: 'Erreur évaluations' });
|
||
}
|
||
});
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// RÉCUPÉRER UNE ÉVALUATION SPÉCIFIQUE
|
||
// ═══════════════════════════════════════════════════════
|
||
app.get('/api/evaluations/:studentId', async (req, res) => {
|
||
try {
|
||
const { studentId } = req.params;
|
||
const userRole = req.headers['x-user-role'];
|
||
const userEmail = req.headers['x-user-email'];
|
||
const userCampus = req.headers['x-user-campus'];
|
||
|
||
const pool = await sql.connect(sqlConfig);
|
||
const request = pool.request();
|
||
request.input('studentId', sql.NVarChar, studentId);
|
||
|
||
let query = `
|
||
SELECT
|
||
e.LOGin as id,
|
||
e.NOM as nom,
|
||
e.PRENOM as prenom,
|
||
e.Campus as campus,
|
||
e.NOM_DIP as formation,
|
||
e.NOM_DIP as nomDip,
|
||
e.Annee as annee,
|
||
e.userPrincipalName as formateurEmail,
|
||
COALESCE(ev.auto_evaluation, 'NON') as autoEvaluation,
|
||
COALESCE(ev.evaluation_tuteur, 'NON') as evaluationTuteur,
|
||
ev.date_action as dateAction,
|
||
ev.date_action_rre as dateActionRRE,
|
||
ev.commentaire_formateur as commentaireFormateur,
|
||
ev.date_eval_tuteur as dateEvalTuteur,
|
||
ev.commentaire_rre as commentaireRRE,
|
||
ev.commentaire,
|
||
ev.updated_at as lastUpdate
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant] e
|
||
LEFT JOIN [dbo].[tbl_Evaluations] ev ON e.LOGin = ev.student_id
|
||
WHERE e.LOGin = @studentId
|
||
`;
|
||
|
||
if (userRole === 'formateur' && userEmail) {
|
||
query += ` AND e.userPrincipalName = @userEmail`;
|
||
request.input('userEmail', sql.NVarChar, userEmail);
|
||
} else if ((userRole === 'rre' || userRole === 'super' || userRole === 'lecteur') && userCampus) {
|
||
query += ` AND e.Campus = @userCampus`;
|
||
request.input('userCampus', sql.NVarChar, userCampus);
|
||
}
|
||
|
||
const evalResult = await request.query(query);
|
||
|
||
if (evalResult.recordset.length === 0) {
|
||
return res.status(404).json({ error: 'Étudiant non trouvé ou non autorisé' });
|
||
}
|
||
|
||
const record = evalResult.recordset[0];
|
||
|
||
const historyResult = await pool.request()
|
||
.input('studentId', sql.NVarChar, studentId)
|
||
.query(`
|
||
SELECT
|
||
action_type as action,
|
||
action_description as details,
|
||
author_name as author,
|
||
created_at as date
|
||
FROM [dbo].[tbl_Historique_Evaluations]
|
||
WHERE record_id = @studentId
|
||
ORDER BY created_at DESC
|
||
`);
|
||
|
||
res.json({
|
||
...record,
|
||
email: `${record.id}@example.com`,
|
||
campusPromo: record.campus,
|
||
history: historyResult.recordset
|
||
});
|
||
|
||
console.log('✅ Détail étudiant envoyé:', studentId);
|
||
} catch (error) {
|
||
console.error('❌ Erreur détail évaluation:', error);
|
||
res.status(500).json({ error: 'Erreur détail évaluation' });
|
||
}
|
||
});
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// METTRE À JOUR UNE ÉVALUATION (VERSION CORRIGÉE)
|
||
// Historique : enregistre TOUTES les valeurs à chaque action
|
||
// ═══════════════════════════════════════════════════════
|
||
app.put('/api/evaluations/:studentId', async (req, res) => {
|
||
try {
|
||
const { studentId } = req.params;
|
||
const updates = req.body.updates;
|
||
const author = req.body.author;
|
||
|
||
const effectiveUser = getEffectiveUser(req.headers);
|
||
const { email, role, campus, isSuperUser, isLecteur } = effectiveUser;
|
||
|
||
// 🔥 BLOQUER LES LECTEURS
|
||
if (isLecteur) {
|
||
console.log(`🚫 Tentative de modification refusée: ${email} (lecteur)`);
|
||
return res.status(403).json({
|
||
error: 'Vous n\'avez pas les droits de modification',
|
||
message: 'Votre rôle est en lecture seule'
|
||
});
|
||
}
|
||
|
||
const pool = await sql.connect(sqlConfig);
|
||
|
||
// Vérification des accès (sauf super-user)
|
||
if (!isSuperUser) {
|
||
if (role === 'formateur' && email) {
|
||
const accessCheck = await pool.request()
|
||
.input('studentId', sql.NVarChar, studentId)
|
||
.input('userEmail', sql.NVarChar, email)
|
||
.query(`
|
||
SELECT COUNT(*) as count
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE LOGin = @studentId AND userPrincipalName = @userEmail
|
||
`);
|
||
|
||
if (accessCheck.recordset[0].count === 0) {
|
||
console.log(`🚫 Accès refusé: ${email} n'a pas accès à ${studentId}`);
|
||
return res.status(403).json({ error: 'Accès non autorisé à cet étudiant' });
|
||
}
|
||
} else if (role === 'rre' && campus) {
|
||
const accessCheck = await pool.request()
|
||
.input('studentId', sql.NVarChar, studentId)
|
||
.input('userCampus', sql.NVarChar, campus)
|
||
.query(`
|
||
SELECT COUNT(*) as count
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE LOGin = @studentId AND Campus = @userCampus
|
||
`);
|
||
|
||
if (accessCheck.recordset[0].count === 0) {
|
||
console.log(`🚫 Accès refusé: RRE ${email} n'a pas accès à ${studentId}`);
|
||
return res.status(403).json({ error: 'Accès non autorisé à cet étudiant' });
|
||
}
|
||
}
|
||
} else {
|
||
console.log(`🎭 SUPER-USER en action: ${email} modifie ${studentId}`);
|
||
}
|
||
|
||
// Récupérer l'enregistrement actuel
|
||
const currentResult = await pool.request()
|
||
.input('studentId', sql.NVarChar, studentId)
|
||
.query(`SELECT * FROM [dbo].[tbl_Evaluations] WHERE student_id = @studentId`);
|
||
|
||
const currentRecord = currentResult.recordset[0];
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// CAS 1 : CRÉATION
|
||
// ═══════════════════════════════════════════════════════
|
||
if (!currentRecord) {
|
||
const studentInfo = await pool.request()
|
||
.input('studentId', sql.NVarChar, studentId)
|
||
.query(`
|
||
SELECT TOP 1 LOGin, NOM, PRENOM, Campus, NOM_DIP, Annee
|
||
FROM [dbo].[v_R_Promo_Formateur_Etudiant]
|
||
WHERE LOGin = @studentId
|
||
`);
|
||
|
||
if (studentInfo.recordset.length === 0) {
|
||
return res.status(404).json({ error: 'Étudiant non trouvé' });
|
||
}
|
||
|
||
const student = studentInfo.recordset[0];
|
||
const today = new Date().toISOString().split('T')[0];
|
||
|
||
await pool.request()
|
||
.input('student_id', sql.NVarChar, studentId)
|
||
.input('nom', sql.NVarChar, student.NOM || '')
|
||
.input('prenom', sql.NVarChar, student.PRENOM || '')
|
||
.input('campus', sql.NVarChar, student.Campus || '')
|
||
.input('formation', sql.NVarChar, student.NOM_DIP || '')
|
||
.input('promo_nom_dip', sql.NVarChar, student.NOM_DIP || '')
|
||
.input('annee', sql.NVarChar, String(student.Annee ?? ''))
|
||
.input('auto_eval', sql.NVarChar, updates.autoEvaluation || 'NON')
|
||
.input('eval_tuteur', sql.NVarChar, updates.evaluationTuteur || 'NON')
|
||
.input('date_action', sql.Date, updates.dateAction || (role === 'formateur' || isSuperUser ? today : null))
|
||
.input('date_action_rre', sql.Date, updates.dateActionRRE || (role === 'rre' || isSuperUser ? today : null))
|
||
.input('commentaire_formateur', sql.NVarChar, updates.commentaireFormateur || null)
|
||
.input('date_eval_tuteur', sql.Date, updates.dateEvalTuteur || null)
|
||
.input('commentaire_rre', sql.NVarChar, updates.commentaireRRE || null)
|
||
.input('commentaire', sql.NVarChar, updates.commentaire || null)
|
||
.input('created_by', sql.NVarChar, author.email)
|
||
.query(`
|
||
INSERT INTO [dbo].[tbl_Evaluations]
|
||
(student_id, nom, prenom, campus, formation, promo_nom_dip, annee,
|
||
auto_evaluation, evaluation_tuteur, date_action, date_action_rre,
|
||
commentaire_formateur, date_eval_tuteur, commentaire_rre,
|
||
commentaire, created_by, updated_by)
|
||
VALUES
|
||
(@student_id, @nom, @prenom, @campus, @formation, @promo_nom_dip, @annee,
|
||
@auto_eval, @eval_tuteur, @date_action, @date_action_rre,
|
||
@commentaire_formateur, @date_eval_tuteur, @commentaire_rre,
|
||
@commentaire, @created_by, @created_by)
|
||
`);
|
||
|
||
// Log historique complet pour la création
|
||
await logHistoryComplete({
|
||
record_id: studentId,
|
||
action_type: 'CREATION',
|
||
action_description: `Création du dossier d'évaluation${isSuperUser ? ' [SUPER-USER]' : ''}`,
|
||
author_email: author.email,
|
||
author_name: author.name,
|
||
author_role: author.role,
|
||
campus: student.Campus,
|
||
auto_evaluation_new: updates.autoEvaluation || 'NON',
|
||
evaluation_tuteur_new: updates.evaluationTuteur || 'NON',
|
||
date_action_new: updates.dateAction || (role === 'formateur' || isSuperUser ? today : null),
|
||
date_action_rre_new: updates.dateActionRRE || (role === 'rre' || isSuperUser ? today : null),
|
||
commentaire_formateur_new: updates.commentaireFormateur || null,
|
||
date_eval_tuteur_new: updates.dateEvalTuteur || null,
|
||
commentaire_rre_new: updates.commentaireRRE || null,
|
||
commentaire_new: updates.commentaire || null
|
||
});
|
||
|
||
const createdResult = await pool.request()
|
||
.input('studentId', sql.NVarChar, studentId)
|
||
.query(`
|
||
SELECT
|
||
student_id as id, nom, prenom, campus, formation,
|
||
promo_nom_dip as nomDip, annee,
|
||
auto_evaluation as autoEvaluation,
|
||
evaluation_tuteur as evaluationTuteur,
|
||
date_action as dateAction,
|
||
date_action_rre as dateActionRRE,
|
||
commentaire_formateur as commentaireFormateur,
|
||
date_eval_tuteur as dateEvalTuteur,
|
||
commentaire_rre as commentaireRRE,
|
||
commentaire, updated_at as lastUpdate
|
||
FROM [dbo].[tbl_Evaluations]
|
||
WHERE student_id = @studentId
|
||
`);
|
||
|
||
console.log('✅ Nouvelle évaluation créée:', studentId);
|
||
return res.json({
|
||
success: true,
|
||
message: 'Évaluation créée',
|
||
data: createdResult.recordset[0]
|
||
});
|
||
}
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// CAS 2 : MISE À JOUR
|
||
// ═══════════════════════════════════════════════════════
|
||
let updateQuery = 'UPDATE [dbo].[tbl_Evaluations] SET updated_at = GETDATE(), updated_by = @updated_by';
|
||
const request = pool.request()
|
||
.input('studentId', sql.NVarChar, studentId)
|
||
.input('updated_by', sql.NVarChar, author.email);
|
||
|
||
let hasChanges = false;
|
||
|
||
const canEditFormateur = role === 'formateur' || isSuperUser;
|
||
const canEditRRE = role === 'rre' || isSuperUser;
|
||
|
||
// ── Construire les valeurs finales (après mise à jour) ──
|
||
// On part des valeurs actuelles et on applique les modifications
|
||
let finalAutoEval = currentRecord.auto_evaluation || 'NON';
|
||
let finalEvalTuteur = currentRecord.evaluation_tuteur || 'NON';
|
||
let finalDateAction = currentRecord.date_action
|
||
? currentRecord.date_action.toISOString().split('T')[0]
|
||
: null;
|
||
let finalDateActionRRE = currentRecord.date_action_rre
|
||
? currentRecord.date_action_rre.toISOString().split('T')[0]
|
||
: null;
|
||
let finalDateEvalTuteur = currentRecord.date_eval_tuteur
|
||
? currentRecord.date_eval_tuteur.toISOString().split('T')[0]
|
||
: null;
|
||
let finalCommentaireFormateur = currentRecord.commentaire_formateur || null;
|
||
let finalCommentaireRRE = currentRecord.commentaire_rre || null;
|
||
let finalCommentaire = currentRecord.commentaire || null;
|
||
|
||
// Auto-évaluation
|
||
if (canEditFormateur && updates.autoEvaluation !== undefined && updates.autoEvaluation !== currentRecord.auto_evaluation) {
|
||
updateQuery += ', auto_evaluation = @auto_eval';
|
||
request.input('auto_eval', sql.NVarChar, updates.autoEvaluation);
|
||
finalAutoEval = updates.autoEvaluation;
|
||
hasChanges = true;
|
||
}
|
||
|
||
// Évaluation tuteur
|
||
if (canEditFormateur && updates.evaluationTuteur !== undefined && updates.evaluationTuteur !== currentRecord.evaluation_tuteur) {
|
||
updateQuery += ', evaluation_tuteur = @eval_tuteur';
|
||
request.input('eval_tuteur', sql.NVarChar, updates.evaluationTuteur);
|
||
finalEvalTuteur = updates.evaluationTuteur;
|
||
hasChanges = true;
|
||
}
|
||
|
||
// Date action formateur
|
||
if (canEditFormateur) {
|
||
if (updates.autoEvaluation !== undefined ||
|
||
updates.evaluationTuteur !== undefined ||
|
||
updates.commentaireFormateur !== undefined ||
|
||
updates.dateEvalTuteur !== undefined ||
|
||
updates.commentaire !== undefined ||
|
||
updates.dateAction !== undefined) {
|
||
|
||
const today = updates.dateAction || new Date().toISOString().split('T')[0];
|
||
const currentDateAction = currentRecord.date_action
|
||
? currentRecord.date_action.toISOString().split('T')[0]
|
||
: null;
|
||
|
||
if (currentDateAction !== today) {
|
||
updateQuery += ', date_action = @date_action';
|
||
request.input('date_action', sql.Date, today);
|
||
finalDateAction = today;
|
||
hasChanges = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Commentaire formateur
|
||
if (canEditFormateur && updates.commentaireFormateur !== undefined && updates.commentaireFormateur !== currentRecord.commentaire_formateur) {
|
||
updateQuery += ', commentaire_formateur = @commentaire_formateur';
|
||
request.input('commentaire_formateur', sql.NVarChar, updates.commentaireFormateur || null);
|
||
finalCommentaireFormateur = updates.commentaireFormateur || null;
|
||
hasChanges = true;
|
||
}
|
||
|
||
// Date évaluation tuteur
|
||
if (canEditFormateur && updates.dateEvalTuteur !== undefined) {
|
||
const currentDateEvalTuteur = currentRecord.date_eval_tuteur
|
||
? currentRecord.date_eval_tuteur.toISOString().split('T')[0]
|
||
: null;
|
||
const newDateEvalTuteur = updates.dateEvalTuteur || null;
|
||
|
||
if (currentDateEvalTuteur !== newDateEvalTuteur) {
|
||
updateQuery += ', date_eval_tuteur = @date_eval_tuteur';
|
||
request.input('date_eval_tuteur', sql.Date, newDateEvalTuteur);
|
||
finalDateEvalTuteur = newDateEvalTuteur;
|
||
hasChanges = true;
|
||
}
|
||
}
|
||
|
||
// Date action RRE
|
||
if (canEditRRE) {
|
||
if ((updates.commentaireRRE !== undefined && updates.commentaireRRE.trim() !== '') || updates.dateActionRRE !== undefined) {
|
||
const today = updates.dateActionRRE || new Date().toISOString().split('T')[0];
|
||
const currentDateActionRRE = currentRecord.date_action_rre
|
||
? currentRecord.date_action_rre.toISOString().split('T')[0]
|
||
: null;
|
||
|
||
if (currentDateActionRRE !== today) {
|
||
updateQuery += ', date_action_rre = @date_action_rre';
|
||
request.input('date_action_rre', sql.Date, today);
|
||
finalDateActionRRE = today;
|
||
hasChanges = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Commentaire RRE
|
||
if (canEditRRE && updates.commentaireRRE !== undefined && updates.commentaireRRE !== currentRecord.commentaire_rre) {
|
||
updateQuery += ', commentaire_rre = @commentaire_rre';
|
||
request.input('commentaire_rre', sql.NVarChar, updates.commentaireRRE || null);
|
||
finalCommentaireRRE = updates.commentaireRRE || null;
|
||
hasChanges = true;
|
||
}
|
||
|
||
// Commentaire général
|
||
if (updates.commentaire !== undefined && updates.commentaire !== currentRecord.commentaire) {
|
||
updateQuery += ', commentaire = @commentaire';
|
||
request.input('commentaire', sql.NVarChar, updates.commentaire || null);
|
||
finalCommentaire = updates.commentaire || null;
|
||
hasChanges = true;
|
||
}
|
||
|
||
// Exécuter la mise à jour et l'historique
|
||
if (hasChanges) {
|
||
updateQuery += ' WHERE student_id = @studentId';
|
||
await request.query(updateQuery);
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// 🔥 HISTORIQUE : on enregistre TOUTES les valeurs finales
|
||
// (pas seulement les champs modifiés)
|
||
// Comme ça chaque ligne d'historique est complète
|
||
// ═══════════════════════════════════════════════════════
|
||
await logHistoryComplete({
|
||
record_id: studentId,
|
||
action_type: 'Nouvelle saisie',
|
||
action_description: `Mise à jour de l'évaluation${isSuperUser ? ' [SUPER-USER]' : ''}`,
|
||
author_email: author.email,
|
||
author_name: author.name,
|
||
author_role: author.role,
|
||
campus: author.campus,
|
||
// Toujours remplir TOUTES les colonnes _new avec l'état final
|
||
auto_evaluation_old: currentRecord.auto_evaluation || null,
|
||
auto_evaluation_new: finalAutoEval,
|
||
evaluation_tuteur_old: currentRecord.evaluation_tuteur || null,
|
||
evaluation_tuteur_new: finalEvalTuteur,
|
||
date_action_old: currentRecord.date_action || null,
|
||
date_action_new: finalDateAction,
|
||
date_action_rre_old: currentRecord.date_action_rre || null,
|
||
date_action_rre_new: finalDateActionRRE,
|
||
date_eval_tuteur_old: currentRecord.date_eval_tuteur || null,
|
||
date_eval_tuteur_new: finalDateEvalTuteur,
|
||
commentaire_formateur_old: currentRecord.commentaire_formateur || null,
|
||
commentaire_formateur_new: finalCommentaireFormateur,
|
||
commentaire_rre_old: currentRecord.commentaire_rre || null,
|
||
commentaire_rre_new: finalCommentaireRRE,
|
||
commentaire_old: currentRecord.commentaire || null,
|
||
commentaire_new: finalCommentaire
|
||
});
|
||
|
||
console.log(`✅ Évaluation mise à jour: ${studentId}${isSuperUser ? ' [SUPER-USER]' : ''}`);
|
||
} else {
|
||
console.log('ℹ️ Aucune modification détectée pour:', studentId);
|
||
}
|
||
|
||
// Récupérer et renvoyer l'enregistrement mis à jour
|
||
const updatedResult = await pool.request()
|
||
.input('studentId', sql.NVarChar, studentId)
|
||
.query(`
|
||
SELECT
|
||
student_id as id, nom, prenom, campus, formation,
|
||
promo_nom_dip as nomDip, annee,
|
||
auto_evaluation as autoEvaluation,
|
||
evaluation_tuteur as evaluationTuteur,
|
||
date_action as dateAction,
|
||
date_action_rre as dateActionRRE,
|
||
commentaire_formateur as commentaireFormateur,
|
||
date_eval_tuteur as dateEvalTuteur,
|
||
commentaire_rre as commentaireRRE,
|
||
commentaire, updated_at as lastUpdate
|
||
FROM [dbo].[tbl_Evaluations]
|
||
WHERE student_id = @studentId
|
||
`);
|
||
|
||
res.json({
|
||
success: true,
|
||
message: 'Évaluation mise à jour',
|
||
data: updatedResult.recordset[0]
|
||
});
|
||
} catch (error) {
|
||
console.error('❌ Erreur mise à jour:', error);
|
||
res.status(500).json({ error: 'Erreur mise à jour', details: error.message });
|
||
}
|
||
});
|
||
|
||
// ═══════════════════════════════════════════════════════
|
||
// DÉMARRAGE
|
||
// ═══════════════════════════════════════════════════════
|
||
app.listen(3005, () => {
|
||
console.log('\n🚀 Backend démarré sur port 3005');
|
||
console.log('📡 Routes disponibles:');
|
||
console.log(' POST /auth/verify');
|
||
console.log(' GET /api/campus-list');
|
||
console.log(' GET /api/student-list?campus=XXX&promo=XXX');
|
||
console.log(' GET /api/promo-list?campus=XXX');
|
||
console.log(' GET /api/promo-data');
|
||
console.log(' GET /api/evaluations');
|
||
console.log(' GET /api/evaluations/:studentId');
|
||
console.log(' GET /api/history/:studentId');
|
||
console.log(' PUT /api/evaluations/:studentId');
|
||
console.log('\n📋 Règles d\'accès:');
|
||
console.log(' - Lecteur: voit uniquement SON campus (LECTURE SEULE)');
|
||
console.log(' - Super: voit et modifie uniquement SON campus');
|
||
console.log(' - Formateur: voit uniquement SES étudiants');
|
||
console.log(' - RRE: voit uniquement les étudiants de SON campus');
|
||
console.log('\n📜 Historique: UNE LIGNE PAR ACTION avec TOUTES les valeurs\n');
|
||
}); |