diff --git a/scripts/seed-opex.mjs b/scripts/seed-opex.mjs new file mode 100644 index 0000000..7308567 --- /dev/null +++ b/scripts/seed-opex.mjs @@ -0,0 +1,139 @@ +/** + * Script de seed : migration des données OPEX 2025 et 2026 + * depuis les fichiers JSON sources vers la base de données MySQL. + * + * Usage : node scripts/seed-opex.mjs + */ + +import { createRequire } from 'module'; +import { readFileSync } from 'fs'; +import { resolve, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const projectRoot = resolve(__dirname, '..'); + +// Charger les variables d'environnement +dotenv.config({ path: resolve(projectRoot, '.env') }); + +const DATABASE_URL = process.env.DATABASE_URL; +if (!DATABASE_URL) { + console.error('❌ DATABASE_URL manquant dans .env'); + process.exit(1); +} + +// Lire les fichiers JSON sources +const data2025 = JSON.parse(readFileSync(resolve(projectRoot, 'client/src/data_opex_2025.json'), 'utf-8')); +const data2026 = JSON.parse(readFileSync(resolve(projectRoot, 'client/src/data_opex.json'), 'utf-8')); + +async function seedOpex(conn, data) { + const annee = data.annee; + const postes = data.postes; + const etablissements = data.etablissements; // liste ou vide + + console.log(`\n📅 Migration OPEX ${annee} — ${postes.length} postes, ${etablissements?.length ?? 0} établissements`); + + // 1. Vérifier si des données existent déjà pour cette année + const [existing] = await conn.execute( + 'SELECT COUNT(*) as cnt FROM opex_postes WHERE annee = ?', + [annee] + ); + const count = existing[0].cnt; + + if (count > 0) { + console.log(` ⚠️ ${count} postes déjà présents pour ${annee} — suppression et réimport...`); + await conn.execute('DELETE FROM opex_montants_etab WHERE annee = ?', [annee]); + await conn.execute('DELETE FROM opex_postes WHERE annee = ?', [annee]); + } + + // 2. Insérer les postes + const montantKey = `montant_previsionnel_${annee}`; + let nbPostesInseres = 0; + + for (const poste of postes) { + const montant = poste[montantKey] ?? poste.montant_previsionnel ?? null; + await conn.execute( + `INSERT INTO opex_postes + (annee, colIdx, libelle, libelleCourt, libelleDetail, fournisseur, categorie, + type, facturation, modeVentilation, compte, detail, budgetN1, montant, isCustom) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)`, + [ + annee, + poste.col_idx ?? 0, + poste.libelle ?? '', + poste.libelle_court ?? null, + poste.libelle_detail ?? null, + poste.fournisseur ?? null, + poste.categorie ?? null, + poste.type ?? null, + poste.facturation ?? null, + poste.mode_ventilation ?? 'Prorata C 6', + poste.compte ?? null, + poste.detail ?? null, + poste.budget_n1 ?? null, + montant, + ] + ); + nbPostesInseres++; + } + console.log(` ✅ ${nbPostesInseres} postes insérés`); + + // 3. Insérer les montants par établissement (si disponibles) — batch INSERT + if (Array.isArray(etablissements) && etablissements.length > 0) { + const rows = []; + for (const etab of etablissements) { + const code = etab.code; + const montants = etab.montants ?? {}; + for (const [libellePoste, montantVal] of Object.entries(montants)) { + if (montantVal === null || montantVal === undefined) continue; + rows.push([annee, code, libellePoste, montantVal]); + } + } + + // Insérer par lots de 500 pour éviter les limites MySQL + const BATCH_SIZE = 500; + let nbMontants = 0; + for (let i = 0; i < rows.length; i += BATCH_SIZE) { + const batch = rows.slice(i, i + BATCH_SIZE); + const placeholders = batch.map(() => '(?, ?, ?, ?)').join(', '); + const values = batch.flat(); + await conn.execute( + `INSERT INTO opex_montants_etab (annee, etablissementCode, libellePoste, montant) + VALUES ${placeholders}`, + values + ); + nbMontants += batch.length; + } + console.log(` ✅ ${nbMontants} montants établissements insérés`); + } else { + console.log(` ℹ️ Pas de montants par établissement pour ${annee} (seront calculés à la volée)`); + } +} + +async function main() { + console.log('🚀 Démarrage du seed OPEX...'); + console.log(`📡 Connexion à la base de données...`); + + const conn = await mysql.createConnection(DATABASE_URL); + + try { + await seedOpex(conn, data2025); + await seedOpex(conn, data2026); + + // Résumé final + const [total] = await conn.execute('SELECT COUNT(*) as cnt FROM opex_postes'); + const [totalMontants] = await conn.execute('SELECT COUNT(*) as cnt FROM opex_montants_etab'); + console.log(`\n🎉 Seed terminé !`); + console.log(` Total postes en BDD : ${total[0].cnt}`); + console.log(` Total montants établissements : ${totalMontants[0].cnt}`); + } finally { + await conn.end(); + } +} + +main().catch(err => { + console.error('❌ Erreur lors du seed :', err.message); + process.exit(1); +});