const { pool } = require('../database') // Helper: get annee_scolaire_id from code string async function getAnneeScolaireId(conn, code) { const [rows] = await conn.query('SELECT id FROM anneescolaire WHERE code = ?', [code]) return rows.length > 0 ? rows[0].id : null } // Helper: SQL to get latest inscription fields per student const inscriptionJoin = ` LEFT JOIN inscriptions i ON e.id = i.etudiant_id AND i.id = (SELECT MAX(ii.id) FROM inscriptions ii WHERE ii.etudiant_id = e.id) LEFT JOIN anneescolaire a ON i.annee_scolaire_id = a.id LEFT JOIN mentions m ON i.mention_id = m.id ` /** * Insert a new student + create their first inscription */ async function insertEtudiant( nom, prenom, photos, date_de_naissances, niveau, annee_scolaire, status, num_inscription, mention_id, sexe, nationaliter, cin, date_delivrence, annee_bacc, serie, boursier, domaine, contact, parcours ) { const conn = await pool.getConnection() try { await conn.beginTransaction() // 1. Insert permanent info into etudiants const [etudiantResult] = await conn.query( `INSERT INTO etudiants (nom, prenom, photos, date_de_naissances, num_inscription, sexe, cin, date_delivrance, nationalite, annee_bacc, serie, boursier, domaine, contact) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [nom, prenom, photos, date_de_naissances, num_inscription, sexe, cin, date_delivrence, nationaliter, annee_bacc, serie, boursier, domaine, contact] ) const etudiantId = etudiantResult.insertId // 2. Get annee_scolaire_id const annee_scolaire_id = await getAnneeScolaireId(conn, annee_scolaire) if (!annee_scolaire_id) { await conn.rollback() return { success: false, message: 'Année scolaire introuvable: ' + annee_scolaire } } // 3. Insert into inscriptions await conn.query( `INSERT INTO inscriptions (etudiant_id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription) VALUES (?, ?, ?, ?, ?, ?, ?)`, [etudiantId, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription] ) await conn.commit() return { success: true, id: etudiantId } } catch (error) { await conn.rollback() return error } finally { conn.release() } } /** * Get all students filtered by a specific annee_scolaire */ async function getAllEtudiantsByAnnee(annee_scolaire) { const sql = ` SELECT e.id, e.nom, e.prenom, e.photos, e.date_de_naissances, e.sexe, e.cin, e.date_delivrance, e.nationalite, e.annee_bacc, e.serie, e.boursier, e.domaine, e.contact, i.num_inscription, i.niveau, a.code AS annee_scolaire, CAST(i.status AS SIGNED) AS status, i.mention_id, i.parcours, i.id AS inscription_id, m.uniter AS mentionUnite, m.nom AS nomMention FROM etudiants e INNER JOIN inscriptions i ON e.id = i.etudiant_id INNER JOIN anneescolaire a ON i.annee_scolaire_id = a.id AND a.code = ? LEFT JOIN mentions m ON i.mention_id = m.id ORDER BY e.nom ` try { const [rows] = await pool.query(sql, [annee_scolaire]) return rows } catch (error) { return error } } /** * Get all students with ALL their inscriptions (une ligne par inscription) */ async function getAllEtudiants() { const sql = ` SELECT e.id, e.nom, e.prenom, e.photos, e.date_de_naissances, e.sexe, e.cin, e.date_delivrance, e.nationalite, e.annee_bacc, e.serie, e.boursier, e.domaine, e.contact, i.num_inscription, i.id AS inscription_id, a.code AS annee_scolaire, /* Champs actuels depuis la dernière inscription */ cur.niveau, CAST(cur.status AS SIGNED) AS status, cur.mention_id, cur.parcours, m_cur.uniter AS mentionUnite, m_cur.nom AS nomMention FROM etudiants e INNER JOIN inscriptions i ON e.id = i.etudiant_id LEFT JOIN anneescolaire a ON i.annee_scolaire_id = a.id /* Dernière inscription pour les champs actuels */ LEFT JOIN inscriptions cur ON e.id = cur.etudiant_id AND cur.id = (SELECT MAX(ii.id) FROM inscriptions ii WHERE ii.etudiant_id = e.id) LEFT JOIN mentions m_cur ON cur.mention_id = m_cur.id ORDER BY e.nom, a.code DESC ` try { const [rows] = await pool.query(sql) return rows } catch (error) { return error } } /** * Get a single student with their latest inscription data */ async function getSingleEtudiant(id) { const sql = ` SELECT e.*, i.num_inscription, i.niveau, a.code AS annee_scolaire, CAST(i.status AS SIGNED) AS status, i.mention_id, i.parcours, m.uniter AS mentionUnite, m.nom AS nomMention FROM etudiants e ${inscriptionJoin} WHERE e.id = ? ` try { const [rows] = await pool.query(sql, [id]) return rows[0] } catch (error) { return error } } /** * Filter students by their latest inscription niveau */ async function FilterDataByNiveau(niveau) { const sql = ` SELECT e.*, i.num_inscription, i.niveau, a.code AS annee_scolaire, CAST(i.status AS SIGNED) AS status, i.mention_id, i.parcours, m.uniter AS mentionUnite, m.nom AS nomMention FROM etudiants e INNER JOIN inscriptions i ON e.id = i.etudiant_id AND i.id = (SELECT MAX(ii.id) FROM inscriptions ii WHERE ii.etudiant_id = e.id) LEFT JOIN anneescolaire a ON i.annee_scolaire_id = a.id LEFT JOIN mentions m ON i.mention_id = m.id WHERE i.niveau = ? ORDER BY a.code DESC ` try { const [rows] = await pool.query(sql, [niveau]) return rows } catch (error) { return error } } /** * Update a student: permanent fields in etudiants, annual fields in latest inscription */ async function updateEtudiant( nom, prenom, photos, date_de_naissances, niveau, annee_scolaire, status, mention_id, num_inscription, id, sexe, nationalite, cin, date_delivrence, annee_bacc, serie, boursier, domaine, contact, parcours ) { const conn = await pool.getConnection() try { await conn.beginTransaction() // Update permanent fields (sans num_inscription car il est propre à chaque année) await conn.query( `UPDATE etudiants SET nom=?, prenom=?, photos=?, date_de_naissances=?, sexe=?, cin=?, date_delivrance=?, nationalite=?, annee_bacc=?, serie=?, boursier=?, domaine=?, contact=? WHERE id=?`, [nom, prenom, photos, date_de_naissances, sexe, cin, date_delivrence, nationalite, annee_bacc, serie, boursier, domaine, contact, id] ) // Get annee_scolaire_id const annee_scolaire_id = await getAnneeScolaireId(conn, annee_scolaire) if (annee_scolaire_id) { // Chercher l'inscription de cette année spécifique const [insc] = await conn.query( 'SELECT id, num_inscription FROM inscriptions WHERE etudiant_id = ? AND annee_scolaire_id = ?', [id, annee_scolaire_id] ) if (insc.length > 0) { const oldNum = insc[0].num_inscription // Si le num_inscription a changé, sauvegarder l'ancien dans etudiants.num_inscription if (oldNum && oldNum !== num_inscription) { const [etudRow] = await conn.query( 'SELECT num_inscription FROM etudiants WHERE id = ?', [id] ) const existingNums = etudRow[0]?.num_inscription || '' // Ajouter l'ancien numéro s'il n'est pas déjà dans l'historique const allNums = existingNums ? existingNums.split(',').map(n => n.trim()) : [] if (!allNums.includes(oldNum)) { allNums.push(oldNum) } // S'assurer que le nouveau est aussi dedans if (!allNums.includes(num_inscription)) { allNums.push(num_inscription) } await conn.query( 'UPDATE etudiants SET num_inscription = ? WHERE id = ?', [allNums.join(','), id] ) } await conn.query( `UPDATE inscriptions SET niveau=?, mention_id=?, parcours=?, status=?, num_inscription=? WHERE id=?`, [niveau, mention_id, parcours, status, num_inscription, insc[0].id] ) } else { await conn.query( `INSERT INTO inscriptions (etudiant_id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription) VALUES (?, ?, ?, ?, ?, ?, ?)`, [id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription] ) } } await conn.commit() return { success: true, message: 'Étudiant mis à jour avec succès.' } } catch (error) { await conn.rollback() return error } finally { conn.release() } } /** * Get dashboard data */ async function getDataToDashboard() { const query = 'SELECT * FROM niveaus' const query2 = 'SELECT * FROM etudiants' const query3 = 'SELECT DISTINCT a.code AS annee_scolaire FROM inscriptions i JOIN anneescolaire a ON i.annee_scolaire_id = a.id' try { const [niveau] = await pool.query(query) const [etudiants] = await pool.query(query2) const [anne_scolaire] = await pool.query(query3) return { niveau, etudiants, anne_scolaire } } catch (error) { return error } } async function changePDP(photos, id) { const sql = 'UPDATE etudiants SET photos = ? WHERE id = ?' try { const [result] = await pool.query(sql, [photos, id]) if (result.affectedRows === 0) { return { success: false, message: 'Étudiant non trouvé.' } } return { success: true, message: 'Photo mise à jour avec succès.' } } catch (error) { return error } } async function updateParcours(parcours, id) { // Update parcours in the latest inscription const sql = ` UPDATE inscriptions SET parcours = ? WHERE etudiant_id = ? AND id = (SELECT MAX(ii.id) FROM (SELECT id FROM inscriptions WHERE etudiant_id = ?) ii) ` try { const [result] = await pool.query(sql, [parcours, id, id]) if (result.affectedRows === 0) { return { success: false, message: 'Inscription non trouvée.' } } return { success: true, message: 'Parcours mis à jour avec succès.' } } catch (error) { return error } } async function deleteEtudiant(id) { const conn = await pool.getConnection() try { await conn.beginTransaction() // Delete in correct order to respect FK constraints await conn.query('DELETE FROM notes WHERE etudiant_id = ?', [id]) await conn.query('DELETE FROM notesrepech WHERE etudiant_id = ?', [id]) await conn.query('DELETE FROM trancheecolage WHERE etudiant_id = ?', [id]) await conn.query('DELETE FROM inscriptions WHERE etudiant_id = ?', [id]) const [result] = await conn.query('DELETE FROM etudiants WHERE id = ?', [id]) await conn.commit() if (result.affectedRows === 0) { return { success: false, message: 'Étudiant non trouvé.' } } return { success: true, message: 'Étudiant supprimé avec succès.' } } catch (error) { await conn.rollback() return { success: false, error: 'Erreur, veuillez réessayer: ' + error } } finally { conn.release() } } /** * Payer une tranche (Tranche 1, Tranche 2, ou Complète) * 1 seule ligne par étudiant par année scolaire */ async function payerTranche(etudiant_id, annee_scolaire_id, type, montant, num_bordereau) { try { // Vérifier si une ligne existe déjà const [existing] = await pool.query( 'SELECT * FROM trancheecolage WHERE etudiant_id = ? AND annee_scolaire_id = ?', [etudiant_id, annee_scolaire_id] ) if (existing.length === 0) { // Créer une nouvelle ligne let data = { tranche1_montant: 0, tranche1_bordereau: null, tranche2_montant: 0, tranche2_bordereau: null } if (type === 'Tranche 1') { data.tranche1_montant = montant data.tranche1_bordereau = num_bordereau } else if (type === 'Tranche 2') { data.tranche2_montant = montant data.tranche2_bordereau = num_bordereau } else { // Tranche Complète : le montant saisi = total, on partage en 2 const half = Math.round(Number(montant) / 2) data.tranche1_montant = half data.tranche1_bordereau = num_bordereau data.tranche2_montant = Number(montant) - half data.tranche2_bordereau = num_bordereau } const is_paid = (data.tranche1_montant > 0 || data.tranche2_montant > 0) ? 1 : 0 await pool.query( 'INSERT INTO trancheecolage (etudiant_id, annee_scolaire_id, tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau, is_paid) VALUES (?, ?, ?, ?, ?, ?, ?)', [etudiant_id, annee_scolaire_id, data.tranche1_montant, data.tranche1_bordereau, data.tranche2_montant, data.tranche2_bordereau, is_paid] ) return { success: true } } else { // Mettre à jour la ligne existante const row = existing[0] let updates = {} if (type === 'Tranche 1') { updates.tranche1_montant = montant updates.tranche1_bordereau = num_bordereau } else if (type === 'Tranche 2') { updates.tranche2_montant = montant updates.tranche2_bordereau = num_bordereau } else { // Tranche Complète : le montant saisi = total, on partage en 2 const half = Math.round(Number(montant) / 2) updates.tranche1_montant = half updates.tranche1_bordereau = num_bordereau updates.tranche2_montant = Number(montant) - half updates.tranche2_bordereau = num_bordereau } const t1 = updates.tranche1_montant !== undefined ? updates.tranche1_montant : row.tranche1_montant const t2 = updates.tranche2_montant !== undefined ? updates.tranche2_montant : row.tranche2_montant const b1 = updates.tranche1_bordereau !== undefined ? updates.tranche1_bordereau : row.tranche1_bordereau const b2 = updates.tranche2_bordereau !== undefined ? updates.tranche2_bordereau : row.tranche2_bordereau const is_paid = (t1 > 0 || t2 > 0) ? 1 : 0 await pool.query( 'UPDATE trancheecolage SET tranche1_montant = ?, tranche1_bordereau = ?, tranche2_montant = ?, tranche2_bordereau = ?, is_paid = ? WHERE id = ?', [t1, b1, t2, b2, is_paid, row.id] ) return { success: true } } } catch (error) { return { success: false, error: error.message } } } /** * Récupérer la ligne de tranche d'un étudiant (toutes les années) */ async function getTranche(etudiant_id) { const sql = ` SELECT t.*, a.code AS annee_scolaire_code FROM trancheecolage t LEFT JOIN anneescolaire a ON t.annee_scolaire_id = a.id WHERE t.etudiant_id = ? ORDER BY a.code DESC ` try { const [rows] = await pool.query(sql, [etudiant_id]) return rows } catch (error) { return [] } } /** * Modifier une ligne de tranche (les 2 tranches d'un coup) */ async function updateTranche(id, tranche1_montant, tranche1_bordereau, tranche2_montant, tranche2_bordereau) { const t1 = Number(tranche1_montant) || 0 const t2 = Number(tranche2_montant) || 0 const is_paid = (t1 > 0 || t2 > 0) ? 1 : 0 try { const [result] = await pool.query( 'UPDATE trancheecolage SET tranche1_montant = ?, tranche1_bordereau = ?, tranche2_montant = ?, tranche2_bordereau = ?, is_paid = ? WHERE id = ?', [t1, tranche1_bordereau || null, t2, tranche2_bordereau || null, is_paid, id] ) if (result.affectedRows === 0) return { success: false, message: 'Non trouve.' } return { success: true } } catch (error) { return { success: false, error: error.message } } } /** * Supprimer une ligne de tranche */ async function deleteTranche(id) { try { const [result] = await pool.query('DELETE FROM trancheecolage WHERE id = ?', [id]) if (result.affectedRows === 0) return { success: false, message: 'Non trouve.' } return { success: true } } catch (error) { return { success: false, error: error.message } } } /** * Étudiants ayant payé au moins 1 tranche pour une année scolaire */ async function getEtudiantsWithPaidTranche(annee_scolaire_code) { const sql = ` SELECT t.etudiant_id FROM trancheecolage t INNER JOIN anneescolaire a ON t.annee_scolaire_id = a.id WHERE a.code = ? AND t.is_paid = 1 ` try { const [rows] = await pool.query(sql, [annee_scolaire_code]) return rows.map(r => r.etudiant_id) } catch (error) { return [] } } /** * Réinscription d'un étudiant existant pour une nouvelle année scolaire. * Crée une NOUVELLE inscription sans modifier les inscriptions précédentes. */ async function reinscribeEtudiant( etudiant_id, niveau, annee_scolaire, status, num_inscription, mention_id, parcours ) { const conn = await pool.getConnection() try { await conn.beginTransaction() // 1. Get annee_scolaire_id const annee_scolaire_id = await getAnneeScolaireId(conn, annee_scolaire) if (!annee_scolaire_id) { await conn.rollback() return { success: false, message: 'Année scolaire introuvable: ' + annee_scolaire } } // 2. Vérifier si une inscription existe déjà pour cet étudiant et cette année const [existing] = await conn.query( 'SELECT id FROM inscriptions WHERE etudiant_id = ? AND annee_scolaire_id = ?', [etudiant_id, annee_scolaire_id] ) if (existing.length > 0) { // Même année scolaire : mettre à jour l'inscription existante await conn.query( `UPDATE inscriptions SET niveau=?, mention_id=?, parcours=?, status=?, num_inscription=? WHERE id=?`, [niveau, mention_id, parcours, status, num_inscription, existing[0].id] ) } else { // Nouvelle année scolaire : créer une nouvelle inscription await conn.query( `INSERT INTO inscriptions (etudiant_id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription) VALUES (?, ?, ?, ?, ?, ?, ?)`, [etudiant_id, annee_scolaire_id, niveau, mention_id, parcours, status, num_inscription] ) } await conn.commit() return { success: true, id: etudiant_id } } catch (error) { await conn.rollback() return { success: false, message: error.message } } finally { conn.release() } } module.exports = { insertEtudiant, getAllEtudiants, getAllEtudiantsByAnnee, FilterDataByNiveau, getSingleEtudiant, updateEtudiant, getDataToDashboard, changePDP, updateParcours, payerTranche, getTranche, updateTranche, deleteTranche, deleteEtudiant, getEtudiantsWithPaidTranche, reinscribeEtudiant }