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
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
|
|
}
|
|
|