GTFRH_V1
This commit is contained in:
@@ -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();
|
||||
Reference in New Issue
Block a user