From 60fd2b891e54826abe4982980b8d2d7c6e40de63 Mon Sep 17 00:00:00 2001 From: Manus Date: Thu, 11 Jun 2026 08:48:10 +0000 Subject: [PATCH] =?UTF-8?q?Checkpoint:=20Suppression=20des=206=20lignes=20?= =?UTF-8?q?parasites=20en=20BDD=20(Infog=C3=A9rance,=20S=C3=A9curit=C3=A9,?= =?UTF-8?q?=20Applicatifs=20HEP,=20Applicatifs=20SMR,=20T=C3=A9l=C3=A9phon?= =?UTF-8?q?ie,=20TOTAL)=20+=20filtre=20PARASITE=5FPATTERNS=20dans=20handle?= =?UTF-8?q?FileImport=20pour=20emp=C3=AAcher=20leur=20r=C3=A9insertion=20l?= =?UTF-8?q?ors=20d'un=20import=20Excel.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/DsiOpex.tsx | 11 ++++++- scripts/clean-bases-parasites.mjs | 51 +++++++++++++++++++++++++++++++ scripts/dedup-opex-postes.mjs | 33 ++++++++++++++++++++ todo.md | 4 +++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 scripts/clean-bases-parasites.mjs create mode 100644 scripts/dedup-opex-postes.mjs diff --git a/client/src/pages/DsiOpex.tsx b/client/src/pages/DsiOpex.tsx index 1181de3..784213c 100644 --- a/client/src/pages/DsiOpex.tsx +++ b/client/src/pages/DsiOpex.tsx @@ -521,8 +521,17 @@ function ClesRepartitionView({ annee, basesRepartitionRaw, isLoadingBases, isErr } // ── Construction des lignes à importer ─────────────────────────────── + // Mots-clés qui indiquent une ligne parasite (catégorie ou synthèse, pas un établissement) + const PARASITE_PATTERNS = /^(infog|s.curit|t.l.phonie|app global|app hep|app smr|applicatifs|total|ventilation|r.partition|nouveaut)/i; const importRows = data.slice(headerIdx + 1) - .filter(row => row[codeIdx] && String(row[codeIdx]).trim()) + .filter(row => { + const code = String(row[codeIdx] ?? '').trim(); + if (!code) return false; + // Rejeter les lignes dont le code ressemble à un libellé de catégorie + const codeNorm = code.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); + if (PARASITE_PATTERNS.test(codeNorm)) return false; + return true; + }) .map(row => ({ etablissementCode: String(row[codeIdx]).trim(), etablissementNom: nomIdx >= 0 ? String(row[nomIdx]).trim() || null : null, diff --git a/scripts/clean-bases-parasites.mjs b/scripts/clean-bases-parasites.mjs new file mode 100644 index 0000000..4c22854 --- /dev/null +++ b/scripts/clean-bases-parasites.mjs @@ -0,0 +1,51 @@ +import { createConnection } from 'mysql2/promise'; +import { config } from 'dotenv'; +config(); + +const conn = await createConnection(process.env.DATABASE_URL); + +// Libellés parasites connus (noms de catégories ou lignes de synthèse) +const PARASITES_KEYWORDS = [ + 'Infogérance', 'Infogerance', 'Sécurité', 'Securite', + 'Téléphonie', 'Telephonie', + 'App global', 'App HEP', 'App SMR', + 'Applicatifs', // commence par + 'TOTAL', 'Total', + 'Ventilation', 'Répartition', 'Repartition', + 'Nouveautés', 'Nouveautes', +]; + +// 1. Lister les lignes parasites +const [rows] = await conn.execute( + `SELECT id, annee, etablissementCode, etablissementNom FROM opex_bases_repartition ORDER BY annee, etablissementCode` +); + +console.log('=== Toutes les lignes opex_bases_repartition ==='); +for (const r of rows) { + const code = r.etablissementCode ?? ''; + const nom = r.etablissementNom ?? ''; + const isParasite = PARASITES_KEYWORDS.some(k => + code.toLowerCase().includes(k.toLowerCase()) || + nom.toLowerCase().includes(k.toLowerCase()) + ) || /^(total|ventilation|répartition|applicatifs)/i.test(code.trim()); + if (isParasite) { + console.log(` PARASITE id=${r.id} annee=${r.annee} code="${code}" nom="${nom}"`); + } +} + +// 2. Supprimer les parasites +const [delResult] = await conn.execute( + `DELETE FROM opex_bases_repartition + WHERE etablissementCode REGEXP '^(Infog|S.curit|T.l.phonie|App global|App HEP|App SMR|Applicatifs|TOTAL|Total|Ventilation|R.partition|Nouveaut)' + OR etablissementNom REGEXP '^(Infog|S.curit|T.l.phonie|App global|App HEP|App SMR|Applicatifs|TOTAL|Total|Ventilation|R.partition|Nouveaut)'` +); +console.log(`\nSupprimé ${delResult.affectedRows} lignes parasites.`); + +// 3. Vérification finale +const [remaining] = await conn.execute( + `SELECT annee, COUNT(*) as cnt FROM opex_bases_repartition GROUP BY annee ORDER BY annee` +); +console.log('=== Lignes restantes par année ==='); +console.table(remaining); + +await conn.end(); diff --git a/scripts/dedup-opex-postes.mjs b/scripts/dedup-opex-postes.mjs new file mode 100644 index 0000000..9ff90d4 --- /dev/null +++ b/scripts/dedup-opex-postes.mjs @@ -0,0 +1,33 @@ +import { createConnection } from 'mysql2/promise'; +import { config } from 'dotenv'; +config(); + +const conn = await createConnection(process.env.DATABASE_URL); + +// 1. Voir les doublons +const [doublons] = await conn.execute( + 'SELECT annee, libelle, COUNT(*) as cnt FROM opex_postes GROUP BY annee, libelle HAVING cnt > 1 ORDER BY cnt DESC LIMIT 30' +); +console.log('=== Doublons (annee, libelle) ==='); +console.table(doublons); + +// 2. Total par année +const [totaux] = await conn.execute( + 'SELECT annee, COUNT(*) as cnt FROM opex_postes GROUP BY annee ORDER BY annee' +); +console.log('=== Total postes par année ==='); +console.table(totaux); + +// 3. Libellés qui ressemblent à des catégories (parasites) +const [parasites] = await conn.execute( + `SELECT id, annee, libelle, categorie FROM opex_postes + WHERE libelle IN ('Infogérance','Sécurité','Téléphonie','App global','App HEP','App SMR', + 'Applicatifs communs','Applicatifs PA','Applicatifs HEP','Applicatifs SMR','Nouveautés','TOTAL', + 'Ventilation cout','Ventilation coût') + OR libelle REGEXP '^(total|ventilation|répartition)' + ORDER BY annee, libelle` +); +console.log('=== Libellés parasites en BDD ==='); +console.table(parasites); + +await conn.end(); diff --git a/todo.md b/todo.md index df9a26d..28efef0 100644 --- a/todo.md +++ b/todo.md @@ -61,3 +61,7 @@ - [x] Bug : doublon de ligne dans le tableau Clés de répartition au clic (mutation setBaseRepartition provoque un double rendu) - [x] Fonctionnalité : mode "tout manuel" par établissement dans l'onglet Clés de répartition (toutes les colonnes OPEX de cet établissement saisies manuellement, ignorant le calcul prorata) + +## Corrections (post-checkpoint eb1684f0) + +- [x] Bug : lignes parasites (catégories Infogérance, Sécurité, TOTAL, etc.) dans la vue Établissements OPEX — suppression en BDD + filtre anti-parasites dans l'import