Checkpoint: Ajout de l'onglet 'Clés de répartition' dans DSI OPEX : tableau éditable avec base de répartition standard et base /HEP par établissement, import fichier Excel/CSV, info année (OPEX N → base N-2). Calcul automatique du poste SAAS DUI Pôle HEP branché sur la colonne /HEP. Nouvelle table opex_bases_repartition avec colonne baseRepartitionHep. Seed 2026 : 64 établissements dont 19 HEP. 14 tests passants, 0 erreur TypeScript.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
/**
|
||||
* Seed des bases de répartition OPEX (charges classe 6) pour 2025 et 2026
|
||||
* depuis les fichiers JSON sources.
|
||||
*
|
||||
* Inclut désormais la colonne baseRepartitionHep (pôle HEP uniquement).
|
||||
* Données 2026 extraites du fichier Excel source via extract_bases_repartition.py.
|
||||
*
|
||||
* Usage: node scripts/seed-opex-bases.mjs
|
||||
*/
|
||||
import mysql from 'mysql2/promise';
|
||||
import { readFileSync } from 'fs';
|
||||
@@ -13,41 +18,89 @@ dotenv.config({ path: join(__dirname, '..', '.env') });
|
||||
|
||||
const conn = await mysql.createConnection(process.env.DATABASE_URL);
|
||||
|
||||
const dataFiles = {
|
||||
2025: join(__dirname, '../client/src/data_opex_2025.json'),
|
||||
2026: join(__dirname, '../client/src/data_opex.json'),
|
||||
};
|
||||
|
||||
for (const [annee, filePath] of Object.entries(dataFiles)) {
|
||||
// ── Année 2025 — depuis le JSON source (pas de colonne HEP disponible) ────────
|
||||
{
|
||||
const annee = 2025;
|
||||
const filePath = join(__dirname, '../client/src/data_opex_2025.json');
|
||||
const data = JSON.parse(readFileSync(filePath, 'utf8'));
|
||||
const etabs = data.etablissements || [];
|
||||
|
||||
if (etabs.length === 0) {
|
||||
console.log(`Année ${annee}: aucun établissement trouvé dans le JSON`);
|
||||
continue;
|
||||
} else {
|
||||
await conn.execute('DELETE FROM opex_bases_repartition WHERE annee = ?', [annee]);
|
||||
const values = etabs.map(e => [
|
||||
annee,
|
||||
e.code,
|
||||
e.nom || null,
|
||||
e.base_repartition || 0,
|
||||
0, // baseRepartitionHep non disponible pour 2025
|
||||
]);
|
||||
await conn.query(
|
||||
'INSERT INTO opex_bases_repartition (annee, etablissementCode, etablissementNom, baseRepartition, baseRepartitionHep) VALUES ?',
|
||||
[values]
|
||||
);
|
||||
console.log(`Année ${annee}: ${values.length} bases de répartition insérées (HEP: non disponible → 0)`);
|
||||
console.log(` Total base: ${etabs.reduce((s, e) => s + (e.base_repartition || 0), 0).toLocaleString('fr-FR', { maximumFractionDigits: 0 })} €`);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Année 2026 — depuis le JSON extrait du fichier Excel source ───────────────
|
||||
// Données extraites par /home/ubuntu/extract_bases_repartition.py
|
||||
// Fichier: /home/ubuntu/bases_repartition_2026.json
|
||||
{
|
||||
const annee = 2026;
|
||||
let bases2026;
|
||||
try {
|
||||
bases2026 = JSON.parse(readFileSync('/home/ubuntu/bases_repartition_2026.json', 'utf8'));
|
||||
} catch (e) {
|
||||
console.error('Fichier bases_repartition_2026.json non trouvé. Fallback sur le JSON source.');
|
||||
const filePath = join(__dirname, '../client/src/data_opex.json');
|
||||
const data = JSON.parse(readFileSync(filePath, 'utf8'));
|
||||
bases2026 = (data.etablissements || []).map(e => ({
|
||||
code: e.code,
|
||||
nom: e.nom,
|
||||
base_repartition: e.base_repartition || 0,
|
||||
base_repartition_hep: 0,
|
||||
}));
|
||||
}
|
||||
|
||||
// Supprimer les bases existantes pour cette année
|
||||
await conn.execute('DELETE FROM opex_bases_repartition WHERE annee = ?', [parseInt(annee)]);
|
||||
// Filtrer les lignes parasites (totaux, applicatifs, etc.)
|
||||
const etablissements = bases2026.filter(e => {
|
||||
const code = String(e.code).trim();
|
||||
const nom = String(e.nom || '').trim().toLowerCase();
|
||||
if (code.length < 4) return false;
|
||||
if (nom.includes('total') || nom.includes('applicatif') || nom.includes('téléphonie')) return false;
|
||||
// Exclure les lignes dont le code ressemble à un montant (ex: "504980")
|
||||
if (/^\d{5,}$/.test(code)) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
// Insérer en batch
|
||||
const values = etabs.map(e => [
|
||||
parseInt(annee),
|
||||
e.code,
|
||||
e.nom || null,
|
||||
await conn.execute('DELETE FROM opex_bases_repartition WHERE annee = ?', [annee]);
|
||||
|
||||
const values = etablissements.map(e => [
|
||||
annee,
|
||||
String(e.code).trim(),
|
||||
String(e.nom || '').trim() || null,
|
||||
e.base_repartition || 0,
|
||||
e.base_repartition_hep || 0,
|
||||
]);
|
||||
|
||||
if (values.length > 0) {
|
||||
await conn.query(
|
||||
'INSERT INTO opex_bases_repartition (annee, etablissementCode, etablissementNom, baseRepartition) VALUES ?',
|
||||
'INSERT INTO opex_bases_repartition (annee, etablissementCode, etablissementNom, baseRepartition, baseRepartitionHep) VALUES ?',
|
||||
[values]
|
||||
);
|
||||
}
|
||||
|
||||
const totalBase = etablissements.reduce((s, e) => s + (e.base_repartition || 0), 0);
|
||||
const totalHep = etablissements.reduce((s, e) => s + (e.base_repartition_hep || 0), 0);
|
||||
const nbHep = etablissements.filter(e => (e.base_repartition_hep || 0) > 0).length;
|
||||
|
||||
console.log(`Année ${annee}: ${values.length} bases de répartition insérées`);
|
||||
console.log(` Total base: ${etabs.reduce((s, e) => s + (e.base_repartition || 0), 0).toLocaleString('fr-FR', { maximumFractionDigits: 0 })} €`);
|
||||
console.log(` Total base standard: ${totalBase.toLocaleString('fr-FR', { maximumFractionDigits: 0 })} €`);
|
||||
console.log(` Total base HEP: ${totalHep.toLocaleString('fr-FR', { maximumFractionDigits: 0 })} € (${nbHep} établissements HEP)`);
|
||||
}
|
||||
|
||||
await conn.end();
|
||||
console.log('\nSeed bases de répartition terminé.');
|
||||
console.log('\n✅ Seed bases de répartition terminé.');
|
||||
|
||||
Reference in New Issue
Block a user