Checkpoint: Seed des données OPEX depuis les fichiers JSON sources vers la base de données MySQL. 33 postes 2025 + 35 postes 2026 + 2030 montants établissements 2026 insérés. Script scripts/seed-opex.mjs créé pour réimport futur. La page DSI OPEX charge désormais les données depuis la BDD sans dépendre du localStorage.

This commit is contained in:
Manus
2026-06-10 13:11:32 +00:00
parent 7495e61b51
commit 7d4ac0721d

139
scripts/seed-opex.mjs Normal file
View File

@@ -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);
});