396 lines
13 KiB
JavaScript
396 lines
13 KiB
JavaScript
const express = require('express');
|
|
const cors = require('cors');
|
|
const sql = require('mssql');
|
|
require('dotenv').config();
|
|
|
|
const app = express();
|
|
const PORT = 3000;
|
|
|
|
// Middleware
|
|
app.use(express.json());
|
|
app.use(cors({
|
|
origin: [
|
|
'http://localhost:3001',
|
|
'http://localhost:8000',
|
|
'http://127.0.0.1:3001',
|
|
'https://mygtf.ensup-adm.net:3001',
|
|
'https://mygtf.ensup-adm.net'
|
|
],
|
|
credentials: true
|
|
}));
|
|
|
|
// Configuration SQL
|
|
const dbConfig = {
|
|
server: process.env.DB_SERVER,
|
|
database: process.env.DB_DATABASE,
|
|
user: process.env.DB_USER,
|
|
password: process.env.DB_PASSWORD,
|
|
options: { encrypt: true, trustServerCertificate: true, enableArithAbort: true }
|
|
};
|
|
|
|
let pool = null;
|
|
let systemStatus = {
|
|
hasFormateurEmailColumn: false,
|
|
hasFormateurView: false,
|
|
canAccessFormateurView: false,
|
|
hasFormateurLocal: false,
|
|
operatingMode: 'unknown'
|
|
};
|
|
|
|
// Connexion DB
|
|
async function connectDatabase() {
|
|
try {
|
|
pool = await sql.connect(dbConfig);
|
|
console.log('Base de données connectée');
|
|
await checkSystemStatus();
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Erreur de connexion DB:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Diagnostic du système
|
|
async function checkSystemStatus() {
|
|
try {
|
|
const columnCheck = await pool.request().query(`
|
|
SELECT COUNT(*) AS count
|
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
WHERE TABLE_NAME = 'declarations'
|
|
AND COLUMN_NAME = 'formateur_email_fk'
|
|
`);
|
|
systemStatus.hasFormateurEmailColumn = columnCheck.recordset[0].count > 0;
|
|
|
|
const viewCheck = await pool.request().query(`
|
|
SELECT COUNT(*) AS count
|
|
FROM INFORMATION_SCHEMA.VIEWS
|
|
WHERE TABLE_NAME = 'v_Formateurs_CD_Declarations'
|
|
`);
|
|
systemStatus.hasFormateurView = viewCheck.recordset[0].count > 0;
|
|
|
|
if (systemStatus.hasFormateurView) {
|
|
try {
|
|
await pool.request().query(`SELECT TOP 1 userPrincipalName FROM dbo.v_Formateurs_CD_Declarations`);
|
|
systemStatus.canAccessFormateurView = true;
|
|
} catch {
|
|
systemStatus.canAccessFormateurView = false;
|
|
}
|
|
}
|
|
|
|
systemStatus.operatingMode = systemStatus.hasFormateurEmailColumn && systemStatus.canAccessFormateurView
|
|
? 'new_with_view'
|
|
: 'legacy_hash';
|
|
|
|
console.log('État système:', systemStatus);
|
|
|
|
} catch (error) {
|
|
console.error('Erreur diagnostic:', error.message);
|
|
systemStatus.operatingMode = 'legacy_hash';
|
|
}
|
|
}
|
|
|
|
// Hash legacy
|
|
function generateHashFromEmail(email) {
|
|
let hash = 0;
|
|
for (let i = 0; i < email.length; i++) {
|
|
const char = email.charCodeAt(i);
|
|
hash = ((hash << 5) - hash) + char;
|
|
hash = hash & hash;
|
|
}
|
|
return Math.abs(hash) % 10000 + 1000;
|
|
}
|
|
|
|
// Conversion heure
|
|
const toSQLTime = (timeStr) => {
|
|
if (!timeStr) return null;
|
|
const [hours, minutes] = timeStr.split(':').map(Number);
|
|
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`;
|
|
};
|
|
|
|
// ROUTES
|
|
|
|
// Diagnostic complet
|
|
app.get('/api/diagnostic', async (req, res) => {
|
|
await checkSystemStatus();
|
|
res.json(systemStatus);
|
|
});
|
|
|
|
// Liste formateurs avec déclaration jointe
|
|
// Route /api/formateurs modifiée pour utiliser formateurs_local
|
|
app.get('/api/formateurs', async (req, res) => {
|
|
try {
|
|
const { email, search } = req.query;
|
|
|
|
let query = `
|
|
SELECT
|
|
userPrincipalName as email,
|
|
CONCAT(givenname, ' ', surname) as nom_complet,
|
|
Campus,
|
|
Contrat
|
|
FROM formateurs_local
|
|
`;
|
|
|
|
const request = pool.request();
|
|
|
|
if (email) {
|
|
query += ` WHERE userPrincipalName = @email`;
|
|
request.input('email', sql.NVarChar, email);
|
|
} else if (search) {
|
|
query += ` WHERE CONCAT(givenname, ' ', surname) LIKE @search OR userPrincipalName LIKE @search`;
|
|
request.input('search', sql.NVarChar, `%${search}%`);
|
|
}
|
|
|
|
query += ` ORDER BY surname, givenname`;
|
|
|
|
const result = await request.query(query);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result.recordset
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Erreur /api/formateurs:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
app.post('/api/save_declaration', async (req, res) => {
|
|
try {
|
|
const { id, date, activityType, hours, description, user, startTime, endTime } = req.body;
|
|
if (!user?.email) return res.status(400).json({ error: 'Email requis' });
|
|
|
|
const heureDebutSQL = toSQLTime(startTime);
|
|
const heureFinSQL = toSQLTime(endTime);
|
|
|
|
// Récupérer l'ID du type "preparation"
|
|
const typeResult = await pool.request()
|
|
.query("SELECT id FROM types_demandes WHERE libelle = 'preparation'");
|
|
|
|
if (typeResult.recordset.length === 0) {
|
|
return res.status(500).json({
|
|
error: 'Configuration manquante: le type "preparation" n\'existe pas dans la base.'
|
|
});
|
|
}
|
|
|
|
const typeDemandeId = typeResult.recordset[0].id;
|
|
|
|
// Vérifier que le formateur existe dans formateurs_local
|
|
const formateur = await pool.request()
|
|
.input('email', sql.NVarChar, user.email)
|
|
.query('SELECT TOP 1 userPrincipalName FROM formateurs_local WHERE userPrincipalName=@email');
|
|
|
|
if (formateur.recordset.length === 0) {
|
|
return res.status(403).json({ error: 'Formateur non trouvé dans la table formateurs_local.' });
|
|
}
|
|
|
|
const formateurEmail = formateur.recordset[0].userPrincipalName;
|
|
|
|
if (id) {
|
|
// MODIFICATION
|
|
console.log('Modification de la déclaration ID:', id);
|
|
await pool.request()
|
|
.input('id', sql.Int, id)
|
|
.input('typeDemandeId', sql.Int, typeDemandeId)
|
|
.input('hours', sql.Float, hours)
|
|
.input('description', sql.NVarChar, description || null)
|
|
.input('heure_debut', sql.VarChar, heureDebutSQL)
|
|
.input('heure_fin', sql.VarChar, heureFinSQL)
|
|
.query(`
|
|
UPDATE declarations
|
|
SET type_demande_id=@typeDemandeId, duree=@hours, description=@description,
|
|
heure_debut=@heure_debut, heure_fin=@heure_fin
|
|
WHERE id=@id
|
|
`);
|
|
console.log('✅ Déclaration modifiée');
|
|
} else {
|
|
// NOUVELLE SAISIE
|
|
console.log('Création nouvelle déclaration pour', formateurEmail, 'le', date);
|
|
await pool.request()
|
|
.input('formateurEmail', sql.NVarChar, formateurEmail)
|
|
.input('typeDemandeId', sql.Int, typeDemandeId)
|
|
.input('date', sql.Date, date)
|
|
.input('hours', sql.Float, hours)
|
|
.input('description', sql.NVarChar, description || null)
|
|
.input('heure_debut', sql.VarChar, heureDebutSQL)
|
|
.input('heure_fin', sql.VarChar, heureFinSQL)
|
|
.query(`
|
|
INSERT INTO declarations
|
|
(formateur_email_fk, type_demande_id, date, duree, description, heure_debut, heure_fin)
|
|
VALUES
|
|
(@formateurEmail, @typeDemandeId, @date, @hours, @description, @heure_debut, @heure_fin)
|
|
`);
|
|
console.log('✅ Nouvelle déclaration créée');
|
|
}
|
|
|
|
res.json({ success: true, message: 'Déclaration sauvegardée' });
|
|
|
|
} catch (error) {
|
|
console.error("Erreur save_declaration:", error);
|
|
res.status(500).json({ error: error.message, stack: error.stack });
|
|
}
|
|
});
|
|
// Récupérer déclarations par email
|
|
app.get('/api/get_declarations_by_email', async (req, res) => {
|
|
try {
|
|
const { email } = req.query;
|
|
if (!email) return res.status(400).json({ error: 'Email requis' });
|
|
|
|
let query = `
|
|
SELECT
|
|
d.id,
|
|
d.formateur_email_fk AS formateur_email,
|
|
CONCAT(f.givenname, ' ', f.surname) AS formateur_nom,
|
|
f.Campus,
|
|
td.libelle AS activityType,
|
|
d.date,
|
|
d.duree,
|
|
d.heure_debut,
|
|
d.heure_fin,
|
|
d.description,
|
|
d.status
|
|
FROM declarations d
|
|
INNER JOIN types_demandes td ON d.type_demande_id = td.id
|
|
LEFT JOIN formateurs_local f ON d.formateur_email_fk = f.userPrincipalName
|
|
WHERE d.formateur_email_fk = @email
|
|
ORDER BY d.date DESC
|
|
`;
|
|
const result = await pool.request().input('email', sql.NVarChar, email).query(query);
|
|
res.json(result.recordset);
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Récupérer toutes les déclarations
|
|
app.get('/api/get_declarations', async (req, res) => {
|
|
try {
|
|
const { user_email, admin } = req.query;
|
|
if (user_email && !admin) return app._router.handle({ ...req, query: { email: user_email } }, res);
|
|
|
|
const query = `
|
|
SELECT
|
|
d.id,
|
|
d.formateur_email_fk AS formateur_email,
|
|
CONCAT(f.givenname, ' ', f.surname) AS formateur_nom,
|
|
f.Campus,
|
|
td.libelle AS activityType,
|
|
d.date,
|
|
d.duree,
|
|
d.heure_debut,
|
|
d.heure_fin,
|
|
d.description,
|
|
d.status
|
|
FROM declarations d
|
|
INNER JOIN types_demandes td ON d.type_demande_id = td.id
|
|
LEFT JOIN formateurs_local f ON d.formateur_email_fk = f.userPrincipalName
|
|
ORDER BY d.date DESC
|
|
`;
|
|
const result = await pool.request().query(query);
|
|
res.json(result.recordset);
|
|
} catch (error) {
|
|
console.error(error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
// Supprimer une déclaration
|
|
app.delete('/api/delete_declaration/:id', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
if (!id) return res.status(400).json({ error: 'ID de la déclaration requis' });
|
|
|
|
// Vérifier si la déclaration existe
|
|
const existing = await pool.request()
|
|
.input('id', sql.Int, id)
|
|
.query('SELECT * FROM declarations WHERE id = @id');
|
|
|
|
if (existing.recordset.length === 0) {
|
|
return res.status(404).json({ error: 'Déclaration non trouvée' });
|
|
}
|
|
|
|
// Supprimer la déclaration
|
|
await pool.request()
|
|
.input('id', sql.Int, id)
|
|
.query('DELETE FROM declarations WHERE id = @id');
|
|
|
|
res.json({ success: true, message: 'Déclaration supprimée' });
|
|
|
|
} catch (error) {
|
|
console.error('Erreur delete_declaration:', error);
|
|
res.status(500).json({ error: error.message });
|
|
}
|
|
});
|
|
|
|
|
|
// ==================== ROUTES GESTION DES CLÔTURES ====================
|
|
|
|
// Vérifier si une date est clôturée
|
|
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); // '2025-01-15' -> '2025-01'
|
|
|
|
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 });
|
|
}
|
|
});
|
|
|
|
// Récupérer toutes les clôtures
|
|
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 });
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// Démarrage serveur
|
|
async function startServer() {
|
|
const dbConnected = await connectDatabase();
|
|
if (!dbConnected) return console.log('Impossible de démarrer sans DB');
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`🚀 Serveur démarré sur http://localhost:${PORT}`);
|
|
console.log('Mode fonctionnement:', systemStatus.operatingMode);
|
|
});
|
|
}
|
|
|
|
process.on('SIGINT', async () => {
|
|
if (pool) await pool.close();
|
|
process.exit(0);
|
|
});
|
|
|
|
startServer();
|