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.
240 lines
8.0 KiB
240 lines
8.0 KiB
const fs = require('fs');
|
|
const path = require('path');
|
|
const XLSX = require('xlsx');
|
|
const { getCompressedDefaultImage } = require('../function/GetImageDefaault');
|
|
const { parse } = require('csv-parse/sync');
|
|
const { pool } = require('../database');
|
|
const dayjs = require('dayjs');
|
|
const customParseFormat = require('dayjs/plugin/customParseFormat');
|
|
dayjs.extend(customParseFormat);
|
|
|
|
// ---------- Fonctions utilitaires ----------
|
|
function fixEncoding(str) {
|
|
if (typeof str !== 'string') return str;
|
|
return str
|
|
.replace(/├®/g, 'é')
|
|
.replace(/├à/g, 'à')
|
|
.replace(/├©/g, 'é')
|
|
.replace(/├ô/g, 'ô')
|
|
.replace(/├ù/g, 'ù')
|
|
.replace(/’/g, "'")
|
|
.replace(/â€/g, '…')
|
|
.replace(/â€/g, '-');
|
|
}
|
|
|
|
function convertToISODate(input) {
|
|
if (!input) return null;
|
|
|
|
if (input instanceof Date && !isNaN(input)) {
|
|
return dayjs(input).format('YYYY-MM-DD');
|
|
}
|
|
|
|
if (typeof input === 'number') {
|
|
return dayjs(new Date((input - 25569) * 86400 * 1000)).format('YYYY-MM-DD');
|
|
}
|
|
|
|
if (typeof input === 'string') {
|
|
const cleanInput = input.trim();
|
|
const versMatch = cleanInput.match(/vers\s*(\d{4})/i);
|
|
if (versMatch) return `${versMatch[1]}-01-01`;
|
|
|
|
const formats = [
|
|
'DD/MM/YYYY', 'D/M/YYYY',
|
|
'YYYY-MM-DD',
|
|
'DD-MM-YYYY', 'D-M-YYYY',
|
|
'MM/DD/YYYY', 'M/D/YYYY',
|
|
'MM-DD-YYYY', 'M-D-YYYY',
|
|
'DD/MM/YY', 'D/M/YY',
|
|
'MM/DD/YY', 'M/D/YY',
|
|
'DD-MM-YY', 'D-M-YY',
|
|
'MM-DD-YY', 'M-D-YY'
|
|
];
|
|
|
|
for (const fmt of formats) {
|
|
const parsed = dayjs(cleanInput, fmt, true);
|
|
if (parsed.isValid()) return parsed.format('YYYY-MM-DD');
|
|
}
|
|
|
|
const freeParse = dayjs(cleanInput);
|
|
if (freeParse.isValid()) return freeParse.format('YYYY-MM-DD');
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// Vérifie année bissextile
|
|
function isLeapYear(year) {
|
|
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
|
|
}
|
|
|
|
// ---------- UPDATE étudiant ----------
|
|
async function updateEtudiant(row) {
|
|
const fields = [];
|
|
const params = [];
|
|
|
|
function addFieldIfValue(field, value) {
|
|
if (value !== undefined && value !== null && value !== '') {
|
|
fields.push(`${field} = ?`);
|
|
params.push(value);
|
|
}
|
|
}
|
|
|
|
addFieldIfValue('nom', row.nom);
|
|
addFieldIfValue('prenom', row.prenom);
|
|
addFieldIfValue('date_de_naissances', convertToISODate(row.date_naissance));
|
|
addFieldIfValue('niveau', row.niveau);
|
|
addFieldIfValue('annee_scolaire', row.annee_scolaire);
|
|
addFieldIfValue('status', row.code_redoublement);
|
|
addFieldIfValue('mention_id', row.mention);
|
|
addFieldIfValue('num_inscription', row.num_inscription?.toString());
|
|
addFieldIfValue('sexe', row.sexe);
|
|
addFieldIfValue('date_delivrance', convertToISODate(row.date_de_delivrance));
|
|
addFieldIfValue('nationalite', row.nationaliter);
|
|
addFieldIfValue('annee_bacc', parseInt(row.annee_baccalaureat, 10));
|
|
addFieldIfValue('serie', row.serie);
|
|
addFieldIfValue('boursier', row.boursier);
|
|
addFieldIfValue('domaine', fixEncoding(row.domaine));
|
|
addFieldIfValue('contact', row.contact);
|
|
|
|
if (fields.length === 0) return { success: false, error: 'Aucun champ valide à mettre à jour' };
|
|
|
|
let sql, whereParams;
|
|
|
|
if (row.cin && row.cin.toString().trim() !== '') {
|
|
sql = `UPDATE etudiants SET ${fields.join(', ')} WHERE cin = ?`;
|
|
whereParams = [row.cin];
|
|
} else {
|
|
sql = `UPDATE etudiants SET ${fields.join(', ')} WHERE LOWER(TRIM(nom)) = ? AND LOWER(TRIM(prenom)) = ?`;
|
|
whereParams = [row.nom.toLowerCase().trim(), row.prenom.toLowerCase().trim()];
|
|
}
|
|
|
|
try {
|
|
const [result] = await pool.query(sql, [...params, ...whereParams]);
|
|
return { success: true, affectedRows: result.affectedRows };
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
// ---------- INSERT multiple étudiants ----------
|
|
async function insertMultipleEtudiants(etudiants) {
|
|
if (!etudiants || etudiants.length === 0) return { success: true, affectedRows: 0 };
|
|
|
|
const sql = `
|
|
INSERT INTO etudiants (
|
|
nom, prenom, photos, date_de_naissances, niveau, annee_scolaire, status,
|
|
mention_id, num_inscription, sexe, cin, date_delivrance, nationalite,
|
|
annee_bacc, serie, boursier, domaine, contact, parcours
|
|
) VALUES ?
|
|
`;
|
|
|
|
const values = etudiants.map(row => [
|
|
row.nom,
|
|
row.prenom,
|
|
getCompressedDefaultImage(),
|
|
convertToISODate(row.date_naissance),
|
|
row.niveau,
|
|
row.annee_scolaire,
|
|
row.code_redoublement,
|
|
row.mention,
|
|
row.num_inscription.toString(),
|
|
row.sexe,
|
|
row.cin || null,
|
|
convertToISODate(row.date_de_delivrance),
|
|
row.nationaliter,
|
|
parseInt(row.annee_baccalaureat, 10),
|
|
row.serie,
|
|
row.boursier,
|
|
fixEncoding(row.domaine),
|
|
row.contact,
|
|
null
|
|
]);
|
|
|
|
try {
|
|
const [result] = await pool.query(sql, [values]);
|
|
return { success: true, affectedRows: result.affectedRows };
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
// ---------- IMPORT fichier ----------
|
|
async function importFileToDatabase(filePath) {
|
|
let records;
|
|
const ext = path.extname(filePath).toLowerCase();
|
|
|
|
if (ext === '.xlsx') {
|
|
const workbook = XLSX.readFile(filePath);
|
|
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
|
records = XLSX.utils.sheet_to_json(worksheet, { defval: '' });
|
|
} else if (ext === '.csv') {
|
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
records = parse(content, { columns: true, skip_empty_lines: true });
|
|
} else {
|
|
return { error: true, message: 'Format de fichier non supporté' };
|
|
}
|
|
|
|
// Vérifier champs obligatoires
|
|
const requiredFields = [
|
|
'nom', 'date_naissance', 'niveau', 'annee_scolaire',
|
|
'mention', 'num_inscription', 'nationaliter', 'sexe',
|
|
'annee_baccalaureat', 'serie', 'code_redoublement',
|
|
'boursier', 'domaine'
|
|
];
|
|
|
|
for (const [i, row] of records.entries()) {
|
|
for (const f of requiredFields) {
|
|
if (!row[f]) return { error: true, message: `Le champ '${f}' est manquant à la ligne ${i + 2}` };
|
|
}
|
|
}
|
|
|
|
const [mentionRows] = await pool.query('SELECT * FROM mentions');
|
|
const [statusRows] = await pool.query('SELECT * FROM status');
|
|
|
|
const etudiantsToInsert = [];
|
|
const doublons = [];
|
|
|
|
for (const row of records) {
|
|
row.date_naissance = convertToISODate(row.date_naissance);
|
|
|
|
// Mapping mention
|
|
const matchedMention = mentionRows.find(
|
|
m => m.nom.toUpperCase() === row.mention.toUpperCase() || m.uniter.toUpperCase() === row.mention.toUpperCase()
|
|
);
|
|
if (matchedMention) row.mention = matchedMention.id;
|
|
|
|
// Mapping status
|
|
row.code_redoublement = (row.code_redoublement ? row.code_redoublement.trim().substring(0, 1) : 'N');
|
|
const statusMatch = statusRows.find(s => s.nom.toLowerCase().startsWith(row.code_redoublement.toLowerCase()));
|
|
if (statusMatch) row.code_redoublement = statusMatch.id;
|
|
|
|
// Détection doublons (ignorer CIN vide)
|
|
let existing;
|
|
if (row.cin && row.cin.toString().trim() !== '') {
|
|
[existing] = await pool.query('SELECT * FROM etudiants WHERE cin = ?', [row.cin]);
|
|
} else {
|
|
[existing] = await pool.query(
|
|
'SELECT * FROM etudiants WHERE LOWER(TRIM(nom)) = ? AND LOWER(TRIM(prenom)) = ?',
|
|
[row.nom.toLowerCase().trim(), row.prenom.toLowerCase().trim()]
|
|
);
|
|
}
|
|
|
|
if (existing.length > 0) {
|
|
doublons.push({ nom: row.nom, prenom: row.prenom, cin: row.cin });
|
|
const updateResult = await updateEtudiant(row);
|
|
if (!updateResult.success) return { error: true, message: `Erreur update ${row.nom} ${row.prenom}: ${updateResult.error}` };
|
|
} else {
|
|
etudiantsToInsert.push(row);
|
|
}
|
|
}
|
|
|
|
console.log('✅ Nouveaux à insérer :', etudiantsToInsert.map(e => e.nom + ' ' + e.prenom));
|
|
console.log('🔄 Étudiants mis à jour :', doublons.map(e => e.nom + ' ' + e.prenom));
|
|
|
|
const insertResult = await insertMultipleEtudiants(etudiantsToInsert);
|
|
if (!insertResult.success) return { error: true, message: insertResult.error };
|
|
|
|
return { error: false, message: `Importation réussie. ${etudiantsToInsert.length} nouvel(le)(s) inséré(s), ${doublons.length} mis à jour.` };
|
|
}
|
|
|
|
module.exports = { importFileToDatabase };
|
|
|