This commit is contained in:
2025-10-02 12:55:40 +02:00
parent 8317094a4c
commit 297dbc2ae5
15 changed files with 1184 additions and 499 deletions

View File

@@ -5,7 +5,7 @@ const axios = require('axios');
require('dotenv').config();
const app = express();
const PORT = 3002;
const PORT = process.env.PORT || 8000;
// Configuration base de données
const dbConfig = {
@@ -23,16 +23,20 @@ const dbConfig = {
// Configuration Microsoft OAuth
const CLIENT_ID = 'cd99bbea-dcd4-4a76-a0b0-7aeb49931943';
const TENANT_ID = '9840a2a0-6ae1-4688-b03d-d2ec291be0f9';
const REDIRECT_URI = 'http://localhost:5174';
const REDIRECT_URI = 'https://mygtf-rh.ensup-adm.net';
const CLIENT_SECRET = 'F5G8Q~qWNzuMdghyIwTX20cAVjqAK4sz~1uEUaLB';
const GROUP_ID = 'c1ea877c-6bca-4f47-bfad-f223640813a0';
// Middleware
app.use(cors({
origin: ['http://localhost:5174', 'http://localhost:5173', 'http://localhost:3000'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
origin: [
'http://localhost:3001',
'http://localhost:8000',
'http://127.0.0.1:3001',
'https://mygtf-rh.ensup-adm.net:3001',
'https://mygtf-rh.ensup-adm.net'
],
credentials: true
}));
app.use(express.json({ limit: '10mb' }));
@@ -85,23 +89,24 @@ async function checkSystemStatus() {
`);
systemStatus.hasFormateurEmailColumn = columnCheck.recordset[0].count > 0;
// 2. Vérifier si la vue Formateurs existe
// 2. Vérifier si la vue v_Formateurs_CD existe
const viewCheck = await pool.request().query(`
SELECT COUNT(*) as count
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_NAME = 'Formateurs'
WHERE TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = 'v_Formateurs_CD'
`);
systemStatus.hasFormateurView = viewCheck.recordset[0].count > 0;
// 3. Tester l'accès à la vue Formateurs si elle existe
// 3. Tester l'accès à la vue v_Formateurs_CD si elle existe
if (systemStatus.hasFormateurView) {
try {
await pool.request().query(`SELECT TOP 1 userPrincipalName FROM [dbo].[Formateurs]`);
await pool.request().query(`SELECT TOP 1 userPrincipalName FROM [GTF].[dbo].[v_Formateurs_CD]`);
systemStatus.canAccessFormateurView = true;
console.log('✅ Accès à la vue Formateurs: OK (RH)');
console.log('✅ Accès à la vue v_Formateurs_CD: OK (RH)');
} catch (error) {
systemStatus.canAccessFormateurView = false;
console.log('❌ Accès à la vue Formateurs: ERREUR (RH) -', error.message);
console.log('❌ Accès à la vue v_Formateurs_CD: ERREUR (RH) -', error.message);
}
}
@@ -128,8 +133,8 @@ async function checkSystemStatus() {
console.log('📊 État du système RH:');
console.log(` - Colonne formateur_email_fk: ${systemStatus.hasFormateurEmailColumn ? '✅' : '❌'}`);
console.log(` - Vue Formateurs: ${systemStatus.hasFormateurView ? '✅' : '❌'}`);
console.log(` - Accès vue Formateurs: ${systemStatus.canAccessFormateurView ? '✅' : '❌'}`);
console.log(` - Vue v_Formateurs_CD: ${systemStatus.hasFormateurView ? '✅' : '❌'}`);
console.log(` - Accès vue v_Formateurs_CD: ${systemStatus.canAccessFormateurView ? '✅' : '❌'}`);
console.log(` - Table formateurs_local: ${systemStatus.hasFormateurLocal ? '✅' : '❌'}`);
console.log(` - Mode de fonctionnement RH: ${systemStatus.operatingMode}`);
@@ -139,13 +144,33 @@ async function checkSystemStatus() {
}
}
// À ajouter dans votre serveur RH (server.js)
// Fonction utilitaire pour parser Nom_brut en nom et prénom
function parseNomBrut(nomBrut) {
if (!nomBrut) {
return { nom: '', prenom: '' };
}
const parts = nomBrut.trim().split(/\s+/);
if (parts.length > 1) {
return {
nom: parts[0],
prenom: parts.slice(1).join(' ')
};
} else {
return {
nom: parts[0],
prenom: ''
};
}
}
// Route de diagnostic des campus
app.get('/api/debug-campus', async (req, res) => {
try {
// Vérifier les campus distincts dans la vue
const campusResult = await pool.request().query(`
SELECT DISTINCT Campus, COUNT(*) as nb_formateurs
FROM [dbo].[Formateurs]
FROM [GTF].[dbo].[v_Formateurs_CD]
GROUP BY Campus
ORDER BY Campus
`);
@@ -154,19 +179,18 @@ app.get('/api/debug-campus', async (req, res) => {
const sampleResult = await pool.request().query(`
SELECT TOP 10
userPrincipalName,
displayName,
Campus,
surname,
givenname
FROM [dbo].[Formateurs]
ORDER BY Campus, displayName
Nom_brut,
Campus,
Contrat
FROM [GTF].[dbo].[v_Formateurs_CD]
ORDER BY Campus, Nom_brut
`);
res.json({
success: true,
campus_distincts: campusResult.recordset,
echantillon_formateurs: sampleResult.recordset,
message: 'Diagnostic des campus dans la vue Formateurs'
message: 'Diagnostic des campus dans la vue v_Formateurs_CD'
});
} catch (error) {
@@ -233,7 +257,6 @@ app.get('/api/debug-campus-local', async (req, res) => {
}
});
// Fonction pour générer un hash reproductible depuis un email (mode legacy)
function generateHashFromEmail(email) {
let hash = 0;
@@ -449,11 +472,11 @@ app.get('/api/diagnostic', async (req, res) => {
break;
case 'new_with_local':
recommendations.push('⚠️ Fonctionne avec la table locale - pas d\'accès à la vue distante');
recommendations.push('💡 Vérifier les permissions sur HP-TO-O365 pour utiliser la vue');
recommendations.push('💡 Vérifier les permissions sur GTF pour utiliser la vue');
break;
case 'new_email_only':
recommendations.push('⚠️ Mode dégradé - sauvegarde par email mais pas de détails formateurs');
recommendations.push('💡 Restaurer l\'accès à la vue Formateurs ou table formateurs_local');
recommendations.push('💡 Restaurer l\'accès à la vue v_Formateurs_CD ou table formateurs_local');
break;
case 'legacy_hash':
recommendations.push('🔄 Mode compatibilité - utilise l\'ancien système de hash');
@@ -552,7 +575,7 @@ app.get('/api/db-test', async (req, res) => {
let formateurCount = 0;
try {
if (systemStatus.canAccessFormateurView) {
const formateurResult = await pool.request().query('SELECT COUNT(*) as total FROM [dbo].[Formateurs]');
const formateurResult = await pool.request().query('SELECT COUNT(*) as total FROM [GTF].[dbo].[v_Formateurs_CD]');
formateurCount = formateurResult.recordset[0].total;
} else if (systemStatus.hasFormateurLocal) {
const formateurResult = await pool.request().query('SELECT COUNT(*) as total FROM formateurs_local');
@@ -591,6 +614,136 @@ app.put('/api/declarations/:id/status', async (req, res) => {
}
});
// Route pour supprimer une déclaration
app.delete('/api/declarations/:id', async (req, res) => {
try {
const { id } = req.params;
console.log(`🗑️ Tentative de suppression de la déclaration ID: ${id}`);
// Vérifier si la déclaration existe
const existing = await pool.request()
.input('id', sql.Int, id)
.query('SELECT id, formateur_email_fk FROM declarations WHERE id = @id');
if (existing.recordset.length === 0) {
console.log(`❌ Déclaration ${id} introuvable`);
return res.status(404).json({
success: false,
message: 'Déclaration introuvable'
});
}
console.log(`✅ Déclaration ${id} trouvée, suppression en cours...`);
// Supprimer la déclaration
await pool.request()
.input('id', sql.Int, id)
.query('DELETE FROM declarations WHERE id = @id');
console.log(`✅ Déclaration ${id} supprimée avec succès`);
res.json({
success: true,
message: 'Déclaration supprimée avec succès'
});
} catch (error) {
console.error('❌ Erreur suppression déclaration:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// Route pour modifier une déclaration
app.put('/api/declarations/:id', async (req, res) => {
try {
const { id } = req.params;
const { date, duree, heure_debut, heure_fin, description, type_demande_id } = req.body;
console.log(`✏️ Tentative de modification de la déclaration ID: ${id}`);
console.log('Données reçues:', { date, duree, heure_debut, heure_fin, type_demande_id });
// Validation
if (!date || !duree || !type_demande_id) {
console.log('❌ Données manquantes');
return res.status(400).json({
success: false,
message: 'Données manquantes (date, durée, type requis)'
});
}
// Vérifier si la déclaration existe
const existing = await pool.request()
.input('id', sql.Int, id)
.query('SELECT id FROM declarations WHERE id = @id');
if (existing.recordset.length === 0) {
console.log(`❌ Déclaration ${id} introuvable`);
return res.status(404).json({
success: false,
message: 'Déclaration introuvable'
});
}
console.log(`✅ Déclaration ${id} trouvée, mise à jour en cours...`);
// Formater les heures correctement pour SQL Server (ajouter :00 pour les secondes)
const formatTimeForSql = (timeString) => {
if (!timeString) return null;
// Si le format est HH:MM, ajouter :00 pour les secondes
if (timeString.match(/^\d{2}:\d{2}$/)) {
return timeString + ':00';
}
return timeString;
};
const heureDebutFormatted = formatTimeForSql(heure_debut);
const heureFinFormatted = formatTimeForSql(heure_fin);
console.log('Heures formatées:', {
original_debut: heure_debut,
formatted_debut: heureDebutFormatted,
original_fin: heure_fin,
formatted_fin: heureFinFormatted
});
// Mettre à jour la déclaration
await pool.request()
.input('id', sql.Int, id)
.input('date', sql.Date, date)
.input('duree', sql.Float, duree)
.input('heure_debut', sql.VarChar(8), heureDebutFormatted)
.input('heure_fin', sql.VarChar(8), heureFinFormatted)
.input('description', sql.NVarChar, description || null)
.input('type_demande_id', sql.Int, type_demande_id)
.query(`
UPDATE declarations
SET date = @date,
duree = @duree,
heure_debut = ${heureDebutFormatted ? '@heure_debut' : 'NULL'},
heure_fin = ${heureFinFormatted ? '@heure_fin' : 'NULL'},
description = @description,
type_demande_id = @type_demande_id
WHERE id = @id
`);
console.log(`✅ Déclaration ${id} modifiée avec succès`);
res.json({
success: true,
message: 'Déclaration modifiée avec succès'
});
} catch (error) {
console.error('❌ Erreur modification déclaration:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
// Route pour récupérer les déclarations (ADAPTÉE AU NOUVEAU SYSTÈME)
app.get('/api/get_declarations', async (req, res) => {
try {
@@ -600,17 +753,15 @@ app.get('/api/get_declarations', async (req, res) => {
switch (systemStatus.operatingMode) {
case 'new_with_view':
// Avec vue Formateurs
// Avec vue v_Formateurs_CD
result = await pool.request().query(`
SELECT
d.id,
d.utilisateur_id,
d.formateur_email_fk as formateur_email,
f.displayName as formateur_nom_complet,
f.surname as nom,
f.givenname as prenom,
f.Nom_brut as formateur_nom_complet,
f.Campus,
f.departement,
f.Contrat,
td.id as type_demande_id,
td.libelle as activityType,
d.date,
@@ -621,7 +772,7 @@ app.get('/api/get_declarations', async (req, res) => {
d.status
FROM declarations d
INNER JOIN types_demandes td ON d.type_demande_id = td.id
LEFT JOIN [dbo].[Formateurs] f ON d.formateur_email_fk = f.userPrincipalName
LEFT JOIN [GTF].[dbo].[v_Formateurs_CD] f ON d.formateur_email_fk = f.userPrincipalName
ORDER BY d.date DESC
`);
break;
@@ -702,8 +853,32 @@ app.get('/api/get_declarations', async (req, res) => {
// Traitement selon le mode
let processedResults = [];
if (systemStatus.operatingMode.startsWith('new_')) {
// Nouveau système - données déjà enrichies par les jointures
if (systemStatus.operatingMode === 'new_with_view') {
// Nouveau système avec v_Formateurs_CD - parsing du Nom_brut
processedResults = result.recordset.map(row => {
const { nom, prenom } = parseNomBrut(row.formateur_nom_complet);
return {
id: row.id,
utilisateur_id: row.utilisateur_id,
formateur_email: row.formateur_email,
type_demande_id: row.type_demande_id,
date: row.date,
duree: row.duree,
description: row.description,
heure_debut: formatSqlTime(row.heure_debut),
heure_fin: formatSqlTime(row.heure_fin),
status: row.status || 'pending',
activityType: row.activityType,
nom: nom || (row.formateur_email ? row.formateur_email.split('@')[0] : 'Inconnu'),
prenom: prenom,
campus: row.Campus || 'Non défini',
contrat: row.Contrat || '',
formateur_nom_complet: row.formateur_nom_complet || row.formateur_email || 'Utilisateur inconnu'
};
});
} else if (systemStatus.operatingMode === 'new_with_local') {
// Table locale avec structure complète
processedResults = result.recordset.map(row => ({
id: row.id,
utilisateur_id: row.utilisateur_id,
@@ -716,12 +891,30 @@ app.get('/api/get_declarations', async (req, res) => {
heure_fin: formatSqlTime(row.heure_fin),
status: row.status || 'pending',
activityType: row.activityType,
// Informations formateur (peuvent être null si pas de jointure)
nom: row.nom || (row.formateur_email ? row.formateur_email.split('@')[0] : 'Inconnu'),
prenom: row.prenom || '',
campus: row.Campus || 'Non défini',
formateur_nom_complet: row.formateur_nom_complet || row.formateur_email || 'Utilisateur inconnu'
}));
} else if (systemStatus.operatingMode === 'new_email_only') {
// Mode email seulement
processedResults = result.recordset.map(row => ({
id: row.id,
utilisateur_id: row.utilisateur_id,
formateur_email: row.formateur_email,
type_demande_id: row.type_demande_id,
date: row.date,
duree: row.duree,
description: row.description,
heure_debut: formatSqlTime(row.heure_debut),
heure_fin: formatSqlTime(row.heure_fin),
status: row.status || 'pending',
activityType: row.activityType,
nom: row.formateur_email ? row.formateur_email.split('@')[0] : 'Inconnu',
prenom: '',
campus: 'Non défini',
formateur_nom_complet: row.formateur_email || 'Utilisateur inconnu'
}));
} else {
// Ancien système - mapping manuel
const knownMappings = {
@@ -799,7 +992,7 @@ app.get('/api/debug-hash', (req, res) => {
if (!email) {
return res.json({
error: 'Email requis',
example: 'http://localhost:3002/api/debug-hash?email=oimer@ensup.eu'
example: '/api/debug-hash?email=oimer@ensup.eu'
});
}
@@ -812,7 +1005,7 @@ app.get('/api/debug-hash', (req, res) => {
});
});
// Nouvelle route pour les formateurs avec déclarations (ADAPTÉE)
// Nouvelle route pour les formateurs avec déclarations
app.get('/api/formateurs-avec-declarations', async (req, res) => {
try {
let result;
@@ -820,56 +1013,80 @@ app.get('/api/formateurs-avec-declarations', async (req, res) => {
switch (systemStatus.operatingMode) {
case 'new_with_view':
result = await pool.request().query(`
SELECT DISTINCT
f.givenname,
f.surname,
f.displayName,
f.Campus,
f.userPrincipalName,
f.Jobtitle,
f.Contrat,
COUNT(d.id) as nb_declarations
FROM [dbo].[Formateurs] f
LEFT JOIN declarations d ON f.userPrincipalName = d.formateur_email_fk
WHERE (f.Contrat = 'CDD' OR f.Contrat LIKE '%CDD%')
AND d.id IS NOT NULL
GROUP BY f.givenname, f.surname, f.displayName, f.Campus, f.userPrincipalName, f.Jobtitle, f.Contrat
ORDER BY f.surname, f.givenname
`);
SELECT DISTINCT
f.Nom_brut,
f.Campus,
f.userPrincipalName,
f.Contrat,
COUNT(d.id) as nb_declarations
FROM [GTF].[dbo].[v_Formateurs_CD] f
LEFT JOIN declarations d ON f.userPrincipalName = d.formateur_email_fk
WHERE LOWER(f.Contrat) LIKE 'cd%'
AND d.id IS NOT NULL
GROUP BY f.Nom_brut, f.Campus, f.userPrincipalName, f.Contrat
ORDER BY f.Nom_brut
`);
break;
case 'new_with_local':
result = await pool.request().query(`
SELECT DISTINCT
f.givenname,
f.surname,
f.displayName,
f.Campus,
f.userPrincipalName,
f.Jobtitle,
f.Contrat,
COUNT(d.id) as nb_declarations
FROM formateurs_local f
LEFT JOIN declarations d ON f.userPrincipalName = d.formateur_email_fk
WHERE (f.Contrat = 'CDD' OR f.Contrat LIKE '%CDD%')
AND d.id IS NOT NULL
GROUP BY f.givenname, f.surname, f.displayName, f.Campus, f.userPrincipalName, f.Jobtitle, f.Contrat
ORDER BY f.surname, f.givenname
`);
SELECT DISTINCT
f.givenname,
f.surname,
f.displayName,
f.Campus,
f.userPrincipalName,
f.Jobtitle,
f.Contrat,
COUNT(d.id) as nb_declarations
FROM formateurs_local f
LEFT JOIN declarations d ON f.userPrincipalName = d.formateur_email_fk
WHERE LOWER(f.Contrat) LIKE 'cd%'
AND d.id IS NOT NULL
GROUP BY f.givenname, f.surname, f.displayName, f.Campus, f.userPrincipalName, f.Jobtitle, f.Contrat
ORDER BY f.surname, f.givenname
`);
break;
default:
return res.json({
success: false,
message: 'Cette fonctionnalité nécessite le nouveau système',
mode: systemStatus.operatingMode
});
}
const formateurs = result.recordset.map(f => ({
userPrincipalName: f.userPrincipalName,
displayName: f.displayName,
nom: f.surname || '',
prenom: f.givenname || '',
campus: f.Campus || 'Non défini',
poste: f.Jobtitle || '',
contrat: f.Contrat || '',
nbDeclarations: f.nb_declarations,
displayText: `${f.surname || ''} ${f.givenname || ''} (${f.Campus || 'Non défini'}) - CDD`.trim()
}));
let formateurs;
if (systemStatus.operatingMode === 'new_with_view') {
// Parser le Nom_brut pour v_Formateurs_CD
formateurs = result.recordset.map(f => {
const { nom, prenom } = parseNomBrut(f.Nom_brut);
return {
userPrincipalName: f.userPrincipalName,
displayName: f.Nom_brut,
nom: nom,
prenom: prenom,
campus: f.Campus || 'Non défini',
contrat: f.Contrat || '',
nbDeclarations: f.nb_declarations,
displayText: `${f.Nom_brut} (${f.Campus || 'Non défini'}) - CDD`.trim()
};
});
} else {
// Structure normale pour formateurs_local
formateurs = result.recordset.map(f => ({
userPrincipalName: f.userPrincipalName,
displayName: f.displayName,
nom: f.surname || '',
prenom: f.givenname || '',
campus: f.Campus || 'Non défini',
poste: f.Jobtitle || '',
contrat: f.Contrat || '',
nbDeclarations: f.nb_declarations,
displayText: `${f.surname || ''} ${f.givenname || ''} (${f.Campus || 'Non défini'}) - CDD`.trim()
}));
}
res.json({
success: true,
@@ -893,21 +1110,20 @@ app.get('/api/formateurs-vue', async (req, res) => {
console.log(`🔍 Récupération formateurs CDD seulement (mode: ${systemStatus.operatingMode})...`);
let formateurs = [];
let result; // Déclarer result ici !
let result;
if (systemStatus.canAccessFormateurView) {
console.log('Utilisation de la vue Formateurs...');
console.log('Utilisation de la vue v_Formateurs_CD...');
result = await pool.request().query(`
SELECT
userPrincipalName,
displayName,
surname,
givenname,
Nom_brut,
Campus,
Contrat
FROM [dbo].[Formateurs]
WHERE Contrat = 'CDD' OR Contrat LIKE '%CDD%'
ORDER BY surname, givenname
FROM [GTF].[dbo].[v_Formateurs_CD]
WHERE LOWER(Contrat) LIKE 'cd%'
ORDER BY Nom_brut
`);
} else if (systemStatus.hasFormateurLocal) {
console.log('Utilisation de formateurs_local...');
@@ -920,7 +1136,8 @@ app.get('/api/formateurs-vue', async (req, res) => {
Campus,
Contrat
FROM formateurs_local
WHERE Contrat = 'CDD' OR Contrat LIKE '%CDD%'
WHERE LOWER(Contrat) LIKE 'cd%'
ORDER BY surname, givenname
`);
} else {
@@ -939,9 +1156,18 @@ app.get('/api/formateurs-vue', async (req, res) => {
if (result.recordset.length === 0) {
console.log('Aucun formateur CDD trouvé, test sans filtre...');
// Test sans le filtre CDD pour voir s'il y a des formateurs
let testResult;
if (systemStatus.hasFormateurLocal) {
if (systemStatus.canAccessFormateurView) {
testResult = await pool.request().query(`
SELECT TOP 5
userPrincipalName,
Nom_brut,
Campus,
Contrat
FROM [GTF].[dbo].[v_Formateurs_CD]
ORDER BY Nom_brut
`);
} else if (systemStatus.hasFormateurLocal) {
testResult = await pool.request().query(`
SELECT TOP 5
userPrincipalName,
@@ -953,20 +1179,41 @@ app.get('/api/formateurs-vue', async (req, res) => {
FROM formateurs_local
ORDER BY surname, givenname
`);
}
if (testResult) {
console.log('Test sans filtre:', testResult.recordset);
console.log('Types de contrats:', [...new Set(testResult.recordset.map(f => f.Contrat))]);
}
}
formateurs = result.recordset.map(f => ({
userPrincipalName: f.userPrincipalName,
displayName: f.displayName,
nom: f.surname || '',
prenom: f.givenname || '',
campus: f.Campus || 'Non défini',
contrat: f.Contrat || '',
displayText: `${f.surname || ''} ${f.givenname || ''} (${f.Campus || 'Non défini'})`.trim()
}));
if (systemStatus.canAccessFormateurView) {
// Parser Nom_brut pour v_Formateurs_CD
formateurs = result.recordset.map(f => {
const { nom, prenom } = parseNomBrut(f.Nom_brut);
return {
userPrincipalName: f.userPrincipalName,
displayName: f.Nom_brut,
nom: nom,
prenom: prenom,
campus: f.Campus || 'Non défini',
contrat: f.Contrat || '',
displayText: `${f.Nom_brut} (${f.Campus || 'Non défini'})`.trim()
};
});
} else {
// Structure normale pour formateurs_local
formateurs = result.recordset.map(f => ({
userPrincipalName: f.userPrincipalName,
displayName: f.displayName,
nom: f.surname || '',
prenom: f.givenname || '',
campus: f.Campus || 'Non défini',
contrat: f.Contrat || '',
displayText: `${f.surname || ''} ${f.givenname || ''} (${f.Campus || 'Non défini'})`.trim()
}));
}
console.log(`${formateurs.length} formateurs CDD traités`);
@@ -986,7 +1233,8 @@ app.get('/api/formateurs-vue', async (req, res) => {
});
}
});
// ==================== ROUTES MICROSOFT GRAPH (INCHANGÉES) ====================
// ==================== ROUTES MICROSOFT GRAPH ====================
app.post('/api/auth', async (req, res) => {
const { userPrincipalName } = req.body;
@@ -1011,9 +1259,6 @@ app.post('/api/auth', async (req, res) => {
}
});
/**
* Authentifier un utilisateur avec Microsoft Graph
*/
async function authenticateUserWithGraph(userPrincipalName, accessToken) {
try {
const existingUser = await pool.request()
@@ -1080,9 +1325,6 @@ async function authenticateUserWithGraph(userPrincipalName, accessToken) {
}
}
/**
* Route pour extraire 3 personnes du service administratif
*/
app.get('/api/admin-users', async (req, res) => {
console.log('=== Route /api/admin-users appelée (RH) ===');
@@ -1144,9 +1386,6 @@ app.get('/api/admin-users', async (req, res) => {
}
});
/**
* Route pour obtenir tous les membres du groupe
*/
app.get('/api/group-members', async (req, res) => {
console.log('=== Route /api/group-members appelée (RH) ===');
@@ -1223,18 +1462,17 @@ app.get('/api/group-members', async (req, res) => {
}
});
// Route de test à ajouter temporairement
app.get('/api/test-permissions', async (req, res) => {
try {
console.log('Test des permissions sur HP-TO-O365...');
console.log('Test des permissions sur GTF...');
// Test direct de la vue
const result = await pool.request().query(`
SELECT TOP 3
userPrincipalName,
displayName,
Campus
FROM [HP-TO-O365].[dbo].[V_Formateurs_Augmentees]
Nom_brut,
Campus,
Contrat
FROM [GTF].[dbo].[v_Formateurs_CD]
`);
res.json({
@@ -1637,9 +1875,6 @@ app.post('/api/login-hybrid', async (req, res) => {
}
});
/**
* Route pour tester la table RH
*/
app.get('/api/rh-test', async (req, res) => {
try {
if (!pool) {
@@ -1660,9 +1895,117 @@ app.get('/api/rh-test', async (req, res) => {
}
});
/**
* Route pour lister tous les utilisateurs de la table RH
*/
// Routes de clôture
app.get('/api/check-cloture', async (req, res) => {
try {
const { date, campus } = req.query;
if (!date) {
return res.status(400).json({ error: 'Date requise' });
}
const moisAnnee = date.substring(0, 7);
const result = await pool.request()
.input('mois_annee', sql.VarChar, moisAnnee)
.input('campus', sql.VarChar, campus || null)
.query(`
SELECT * FROM clotures_saisie
WHERE mois_annee = @mois_annee
AND (campus = @campus OR campus IS NULL OR @campus IS NULL)
`);
res.json({
cloture: result.recordset.length > 0,
details: result.recordset[0] || null
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get('/api/clotures', async (req, res) => {
try {
const result = await pool.request().query(`
SELECT * FROM clotures_saisie
ORDER BY date_cloture DESC
`);
res.json({
success: true,
clotures: result.recordset
});
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
app.post('/api/cloturer-periode', async (req, res) => {
try {
const { mois_annee, date_debut, date_fin, campus, commentaire, email_rh } = req.body;
if (!mois_annee || !date_debut || !date_fin) {
return res.status(400).json({
success: false,
error: 'Données manquantes'
});
}
const existing = await pool.request()
.input('mois_annee', sql.VarChar, mois_annee)
.input('campus', sql.VarChar, campus || null)
.query(`
SELECT id FROM clotures_saisie
WHERE mois_annee = @mois_annee
AND (campus = @campus OR (campus IS NULL AND @campus IS NULL))
`);
if (existing.recordset.length > 0) {
return res.status(400).json({
success: false,
error: 'Cette période est déjà clôturée'
});
}
await pool.request()
.input('mois_annee', sql.VarChar, mois_annee)
.input('date_debut', sql.Date, date_debut)
.input('date_fin', sql.Date, date_fin)
.input('campus', sql.VarChar, campus || null)
.input('cloture_par', sql.VarChar, email_rh)
.input('commentaire', sql.NVarChar, commentaire || null)
.query(`
INSERT INTO clotures_saisie
(mois_annee, date_debut, date_fin, campus, cloture_par, commentaire)
VALUES (@mois_annee, @date_debut, @date_fin, @campus, @cloture_par, @commentaire)
`);
res.json({
success: true,
message: 'Période clôturée avec succès'
});
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
app.delete('/api/rouvrir-periode/:id', async (req, res) => {
try {
const { id } = req.params;
await pool.request()
.input('id', sql.Int, id)
.query('DELETE FROM clotures_saisie WHERE id = @id');
res.json({
success: true,
message: 'Période réouverte avec succès'
});
} catch (error) {
res.status(500).json({ success: false, error: error.message });
}
});
app.get('/api/rh-users', async (req, res) => {
try {
const limit = req.query.limit ? parseInt(req.query.limit) : 50;
@@ -1710,52 +2053,38 @@ async function startServer() {
console.log(`📊 Mode de fonctionnement RH: ${systemStatus.operatingMode}`);
console.log('');
console.log('Routes disponibles :');
console.log('- GET /api/diagnostic (vérifier l\'état)');
console.log('- POST /api/migrate (appliquer la migration)');
console.log('- GET /api/diagnostic');
console.log('- POST /api/migrate');
console.log('- GET /api/test');
console.log('- GET /api/db-test');
console.log('- GET /api/get_declarations');
console.log('- PUT /api/declarations/:id/status');
console.log('- POST /api/exchange-token (Échange code Microsoft)');
console.log('- POST /api/auth (Microsoft Graph - legacy)');
console.log('- GET /api/admin-users (3 utilisateurs administratifs)');
console.log('- GET /api/group-members (tous les membres du groupe)');
console.log('- GET /api/formateurs-avec-declarations');
console.log('- DELETE /api/declarations/:id');
console.log('- PUT /api/declarations/:id');
console.log('- POST /api/exchange-token');
console.log('- GET /api/formateurs-vue');
console.log('- GET /api/rh-test (test table RH)');
console.log('- GET /api/rh-users (liste utilisateurs RH)');
console.log('- GET /api/clotures');
console.log('- POST /api/cloturer-periode');
console.log('- DELETE /api/rouvrir-periode/:id');
console.log('');
switch (systemStatus.operatingMode) {
case 'new_with_view':
console.log('✅ Système optimal - utilise la vue Formateurs');
console.log('✅ Système optimal - utilise la vue v_Formateurs_CD');
break;
case 'new_with_local':
console.log('⚠️ Mode dégradé - utilise la table formateurs_local');
console.log('💡 Conseil: Vérifier les permissions sur HP-TO-O365');
break;
case 'new_email_only':
console.log('⚠️ Mode minimal - sauvegarde par email sans détails formateurs');
break;
case 'legacy_hash':
console.log('🔄 Mode compatibilité - utilise l\'ancien système de hash');
console.log('💡 Conseil: Appliquer la migration avec POST /api/migrate');
break;
}
if (!CLIENT_SECRET) {
console.warn('⚠️ Variable d\'environnement manquante: CLIENT_SECRET');
console.warn(' Ajoutez CLIENT_SECRET dans votre fichier .env');
} else {
console.log('✅ Configuration Microsoft OAuth OK');
console.log(` Client ID: ${CLIENT_ID}`);
console.log(` Tenant ID: ${TENANT_ID}`);
console.log(` Redirect URI: ${REDIRECT_URI}`);
}
});
}
// Arrêt propre
process.on('SIGINT', async () => {
console.log('Arrêt du serveur RH...');
if (pool) {
@@ -1764,5 +2093,4 @@ process.on('SIGINT', async () => {
process.exit(0);
});
// Démarrer
startServer();