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.
 
 
 

316 lines
10 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);
// ✅ Fonction de correction d'encodage
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;
console.log('🔍 Input original:', input, 'Type:', typeof input);
// Si input est un objet Date valide
if (input instanceof Date && !isNaN(input)) {
const result = dayjs(input).format('YYYY-MM-DD');
console.log('📅 Date object convertie:', result);
return result;
}
// Si input est un nombre (numéro de série Excel)
if (typeof input === 'number') {
// Formule Excel: (numéro - 25569) * 86400 * 1000
const excelDate = new Date((input - 25569) * 86400 * 1000);
const result = dayjs(excelDate).format('YYYY-MM-DD');
console.log('📊 Numéro Excel', input, 'converti en:', result);
return result;
}
// Si input est une chaîne
if (typeof input === 'string') {
const cleanInput = input.trim();
// Cas spécial "vers YYYY"
const versMatch = cleanInput.match(/vers\s*(\d{4})/i);
if (versMatch) {
const result = `${versMatch[1]}-01-01`;
console.log('📝 "Vers" détecté:', result);
return result;
}
// Formats à tester dans l'ordre de priorité
const formats = [
'DD/MM/YYYY', 'D/M/YYYY', // Format français prioritaire
'YYYY-MM-DD', // Format ISO
'DD-MM-YYYY', 'D-M-YYYY', // Format français avec tirets
'MM/DD/YYYY', 'M/D/YYYY', // Format américain
'MM-DD-YYYY', 'M-D-YYYY', // Format américain avec tirets
'DD/MM/YY', 'D/M/YY', // Années courtes
'MM/DD/YY', 'M/D/YY',
'DD-MM-YY', 'D-M-YY',
'MM-DD-YY', 'M-D-YY'
];
// Test avec parsing strict pour éviter les interprétations erronées
for (const format of formats) {
const parsedDate = dayjs(cleanInput, format, true); // true = strict parsing
if (parsedDate.isValid()) {
const result = parsedDate.format('YYYY-MM-DD');
console.log(`✅ Format "${format}" réussi:`, cleanInput, '->', result);
// Vérification supplémentaire pour les dates invalides comme 29/02 en année non-bissextile
if (format.includes('DD/MM') || format.includes('D/M')) {
const day = parsedDate.date();
const month = parsedDate.month() + 1; // dayjs month is 0-indexed
const year = parsedDate.year();
// Vérifier si c'est le 29 février d'une année non-bissextile
if (month === 2 && day === 29 && !isLeapYear(year)) {
console.warn('⚠️ Date invalide détectée: 29 février en année non-bissextile');
return null; // ou retourner une date par défaut
}
}
return result;
}
}
// Si aucun format strict ne fonctionne, essayer le parsing libre en dernier recours
const freeParseDate = dayjs(cleanInput);
if (freeParseDate.isValid()) {
const result = freeParseDate.format('YYYY-MM-DD');
console.log('🆓 Parsing libre réussi:', cleanInput, '->', result);
return result;
}
}
console.error('❌ Impossible de convertir:', input);
return null;
}
// Fonction utilitaire pour vérifier les années bissextiles
function isLeapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
}
// ✅ Mise à jour d'un étudiant existant
async function updateEtudiant(row) {
const sql = `
UPDATE etudiants SET
nom = ?,
prenom = ?,
photos = ?,
date_de_naissances = ?,
niveau = ?,
annee_scolaire = ?,
status = ?,
mention_id = ?,
num_inscription = ?,
sexe = ?,
date_delivrance = ?,
nationalite = ?,
annee_bacc = ?,
serie = ?,
boursier = ?,
domaine = ?,
contact = ?,
parcours = ?
WHERE cin = ? OR (LOWER(nom) = ? AND LOWER(prenom) = ?)
`;
const params = [
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,
convertToISODate(row.date_de_delivrance),
row.nationaliter,
parseInt(row.annee_baccalaureat, 10),
row.serie,
row.boursier,
fixEncoding(row.domaine),
row.contact,
null,
row.cin,
row.nom.toLowerCase().trim(),
row.prenom.toLowerCase().trim()
];
try {
const [result] = await pool.query(sql, params);
console.log(`Update effectué pour CIN ${row.cin} ou nom ${row.nom} ${row.prenom}, affectedRows=${result.affectedRows}`);
return { success: true, affectedRows: result.affectedRows };
} catch (error) {
console.error('❌ Erreur MySQL update :', error.message);
return { success: false, error: error.message };
}
}
// ✅ Insertion réelle multiple
async function insertMultipleEtudiants(etudiants) {
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,
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) {
console.error('❌ Erreur MySQL :', error.message);
return { success: false, error: error.message };
}
}
// ✅ Import fichier vers base
async function importFileToDatabase(filePath) {
const fileExtension = path.extname(filePath).toLowerCase();
let records;
if (fileExtension === '.xlsx') {
const workbook = XLSX.readFile(filePath);
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
// raw: true pour garder les valeurs brutes, surtout pour les dates
records = XLSX.utils.sheet_to_json(worksheet, { defval: ''});
} else if (fileExtension === '.csv') {
const fileContent = fs.readFileSync(filePath, 'utf8');
records = parse(fileContent, { columns: true, skip_empty_lines: true });
}else {
console.error('Unsupported file format.');
return { error: true, message: 'Format de fichier non supporté.' };
}
console.log(`📄 Nombre de lignes : ${records.length}`);
// 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 field of requiredFields) {
if (!row[field]) {
const msg = `Le champ '${field}' est manquant à la ligne ${i + 2}`;
console.error(msg);
return { error: true, message: msg };
}
}
}
const [mentionRows] = await pool.query('SELECT * FROM mentions');
const [statusRows] = await pool.query('SELECT * FROM status');
const etudiantsToInsert = [];
const doublons = [];
console.log(records);
for (const row of records) {
// Mapping mention
console.log('Avant conversion date_naissance:', row.date_naissance);
row.date_naissance = convertToISODate(row.date_naissance);
console.log('Après conversion date_naissance:', row.date_naissance);
const matchedMention = mentionRows.find(
m => m.nom.toUpperCase() === row.mention.toUpperCase() ||
m.uniter.toUpperCase() === row.mention.toUpperCase()
);
if (matchedMention) row.mention = matchedMention.id;
// Gestion code_redoublement -> status id
if (row.code_redoublement) {
row.code_redoublement = row.code_redoublement.trim().substring(0, 1);
} else {
row.code_redoublement = 'N';
}
const statusMatch = statusRows.find(
s => s.nom.toLowerCase().startsWith(row.code_redoublement.toLowerCase())
);
if (statusMatch) row.code_redoublement = statusMatch.id;
// Vérification doublons (extraction complet)
const nomComplet = (row.nom + ' ' + row.prenom).toLowerCase().trim();
const [existing] = await pool.query(
'SELECT * FROM etudiants WHERE LOWER(CONCAT(nom, " ", prenom)) = ? OR cin = ?',
[nomComplet, row.cin]
);
if (existing.length > 0) {
doublons.push({ nom: row.nom, prenom: row.prenom, cin: row.cin });
// Mise à jour
const updateResult = await updateEtudiant(row);
if (!updateResult.success) {
return { error: true, message: `Erreur lors de la mise à jour de ${row.nom} ${row.prenom} : ${updateResult.error}` };
}
continue;
}
etudiantsToInsert.push(row);
}
console.log(etudiantsToInsert);
// Insertion des nouveaux
let insertResult = { success: true, affectedRows: 0 };
if (etudiantsToInsert.length > 0) {
insertResult = await insertMultipleEtudiants(etudiantsToInsert);
if (!insertResult.success) {
return { error: true, message: `Erreur lors de l'insertion : ${insertResult.error}` };
}
}
let msg = `Importation réussie. ${etudiantsToInsert.length} nouvel(le)(s) étudiant(s) inséré(s). ${doublons.length} étudiant(s) mis à jour.`;
return { error: false, message: msg };
}
module.exports = { importFileToDatabase };