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:
73
client/src/lib/opexTrends.ts
Normal file
73
client/src/lib/opexTrends.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Calculs purs des tendances OPEX utilisées par les vignettes de catégories.
|
||||
*
|
||||
* Cette fonction ne dépend volontairement ni de React, ni des fichiers JSON
|
||||
* historiques : la comparaison est toujours faite avec les montants réellement
|
||||
* chargés pour l'exercice précédent.
|
||||
*/
|
||||
export type OpexCategoryLine = {
|
||||
categorie: string | null | undefined;
|
||||
montant: number;
|
||||
};
|
||||
|
||||
export type OpexCategoryTrend = {
|
||||
montantN1: number | null;
|
||||
variationPct: number | null;
|
||||
tendance: "hausse" | "baisse" | "stable" | "new";
|
||||
};
|
||||
|
||||
/** Toute variation strictement inférieure à ce seuil est considérée stable. */
|
||||
export const STABLE_VARIATION_THRESHOLD_PERCENT = 2;
|
||||
|
||||
function toCategoryTotals(lines: OpexCategoryLine[]): Record<string, number> {
|
||||
return lines.reduce<Record<string, number>>((totals, line) => {
|
||||
const category = line.categorie?.trim() || "Autre";
|
||||
totals[category] = (totals[category] ?? 0) + line.montant;
|
||||
return totals;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne les tendances par catégorie pour l'exercice courant.
|
||||
*
|
||||
* Les pourcentages sont arrondis à une décimale pour correspondre à l'affichage
|
||||
* et éviter des différences entre le badge et les données fournies au composant.
|
||||
*/
|
||||
export function calculateCategoryTrends(
|
||||
currentLines: OpexCategoryLine[],
|
||||
previousLines: OpexCategoryLine[],
|
||||
): Record<string, OpexCategoryTrend> {
|
||||
const currentTotals = toCategoryTotals(currentLines);
|
||||
const previousTotals = toCategoryTotals(previousLines);
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(currentTotals).map(([category, currentAmount]) => {
|
||||
const previousAmount = previousTotals[category] ?? 0;
|
||||
|
||||
if (previousAmount === 0) {
|
||||
return [
|
||||
category,
|
||||
{
|
||||
montantN1: null,
|
||||
variationPct: null,
|
||||
tendance: "new" as const,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const rawVariation = ((currentAmount - previousAmount) / previousAmount) * 100;
|
||||
const variationPct = Math.round(rawVariation * 10) / 10;
|
||||
const tendance =
|
||||
Math.abs(variationPct) < STABLE_VARIATION_THRESHOLD_PERCENT
|
||||
? "stable"
|
||||
: variationPct > 0
|
||||
? "hausse"
|
||||
: "baisse";
|
||||
|
||||
return [
|
||||
category,
|
||||
{ montantN1: previousAmount, variationPct, tendance },
|
||||
];
|
||||
}),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user