You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

568 lines
18 KiB

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
}