Checkpoint: Audit de robustesse et maintenabilité : calcul dynamique des tendances OPEX par véritable année N-1, filtrage/validation centralisés des codes établissement, transactions d'import et d'écriture, contraintes uniques CAPEX/OPEX avec déduplication, suppression effective de postes OPEX, nettoyage des gabarits et scripts morts, chargement différé XLSX, documentation métier et 22 tests passants.
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
// ImportModal.tsx — Fenêtre d'import de données (Inventaire, Établissements, Utilisateurs)
|
||||
// Design: Corporate Modernism — Itinova Budget SI
|
||||
|
||||
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { trpc } from '@/lib/trpc';
|
||||
import { useAnnee, getInventaireStorageKey } from '../contexts/AnneeContext';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { useAnnee } from '../contexts/AnneeContext';
|
||||
import {
|
||||
Upload,
|
||||
X,
|
||||
@@ -28,6 +27,9 @@ import { toast } from 'sonner';
|
||||
async function fileToRows(file: File): Promise<string[][]> {
|
||||
const isXlsx = file.name.match(/\.(xlsx|xls)$/i);
|
||||
if (isXlsx) {
|
||||
// XLSX pèse plusieurs centaines de Ko : il n'est requis qu'à l'ouverture
|
||||
// d'un fichier d'import et ne doit pas ralentir l'affichage initial.
|
||||
const XLSX = await import('xlsx');
|
||||
const buffer = await file.arrayBuffer();
|
||||
const wb = XLSX.read(buffer, { type: 'array', cellDates: true });
|
||||
const ws = wb.Sheets[wb.SheetNames[0]];
|
||||
@@ -418,10 +420,6 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
|
||||
h === 'fabricant' || h === 'marque' || h === 'brand' ||
|
||||
h.includes('fabricant') || h.includes('marque')
|
||||
);
|
||||
const statutIdx = rawHeaders.findIndex(h =>
|
||||
h === 'statut' || h === 'status' || h.includes('statut')
|
||||
);
|
||||
|
||||
// Compteurs pour le résumé
|
||||
let nbFixes = 0;
|
||||
let nbPortables = 0;
|
||||
@@ -543,7 +541,7 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
|
||||
});
|
||||
|
||||
// ── Envoyer à la base de données via tRPC ──────────────────────────────────
|
||||
const result = await importInventaireMutation.mutateAsync({ annee, filename: file.name, postes: postesPayload });
|
||||
await importInventaireMutation.mutateAsync({ annee, filename: file.name, postes: postesPayload });
|
||||
|
||||
// ── Upsert établissements dans la BDD ──────────────────────────────────────
|
||||
for (const [code, info] of Array.from(etabData.entries())) {
|
||||
@@ -552,7 +550,6 @@ export function ImportModal({ open, onClose }: ImportModalProps) {
|
||||
} catch { /* ignore les erreurs d'upsert établissement */ }
|
||||
}
|
||||
|
||||
const nbNouveaux = result.nbEtablissements;
|
||||
const details = [
|
||||
`${rows.length - 1} poste${rows.length - 1 > 1 ? 's' : ''}`,
|
||||
`${etabData.size} établissement${etabData.size > 1 ? 's' : ''} détectés`,
|
||||
|
||||
Reference in New Issue
Block a user