fix(reports/cartes): Budget Adherence card filters out all expense categories (signed monthBudget) #112

Closed
opened 2026-04-19 13:28:59 +00:00 by maximus · 0 comments
Owner

Contexte

Sur /reports/cartes, la carte "Adhésion budgétaire" (Budget Adherence) affiche systématiquement « aucune catégorie avec budget ce mois-ci » même quand l'utilisateur a des budgets définis sur des catégories de dépenses.

Cause racine

Dans src/services/reportService.ts (fonction getCartesSnapshot, ~ligne 912) :

const budgetedExpenseRows = budgetRows.filter(
  (r) => r.category_type === "expense" && r.monthBudget > 0 && !r.is_parent,
);

Dans src/services/budgetService.ts ligne 260, monthBudget est signé :

const signFor = (type: string) => (type === "expense" ? -1 : 1);
const monthBudget = rawMonthBudget * sign;  // négatif pour les expense

Le filtre r.monthBudget > 0 est donc toujours faux pour les lignes expense → aucune catégorie ne passe, même avec un budget réel > 0.

Autres filtres impactés

  • Ligne 916 : Math.abs(r.monthActual) <= r.monthBudget — compare |actual| à un budget négatif → toujours faux.
  • Ligne 922–923 : overrunAbs = actual - r.monthBudget, overrunPct = overrunAbs / r.monthBudget — calculs bruités par les signes.
  • Ligne 285 de budgetService.ts (hors scope de ce bug) utilise déjà Math.abs(monthBudget) pour le variation % — c'est la bonne approche à dupliquer.

Fix proposé

Dans reportService.ts, traiter les montants en absolu dans la carte Budget Adherence :

const budgetedExpenseRows = budgetRows.filter(
  (r) => r.category_type === "expense" && Math.abs(r.monthBudget) > 0 && !r.is_parent,
);
const budgetsInTarget = budgetedExpenseRows.filter(
  (r) => Math.abs(r.monthActual) <= Math.abs(r.monthBudget),
).length;

const overruns = budgetedExpenseRows
  .map((r) => {
    const actual = Math.abs(r.monthActual);
    const budget = Math.abs(r.monthBudget);
    const overrunAbs = actual - budget;
    const overrunPct = budget > 0 ? (overrunAbs / budget) * 100 : null;
    return { ...r, actual, budget, overrunAbs, overrunPct };
  })
  .filter((r) => r.overrunAbs > 0)
  // ...tri existant
  ;

Fichiers concernés

  • src/services/reportService.ts (lignes ~912–942)
  • src/services/reportService.cartes.test.ts — ajouter un test de régression avec une catégorie expense ayant monthBudget = -100 et monthActual = -80, qui doit passer le filtre (dans le fix) et compter comme "in target".

Critères d'acceptation

  • Une catégorie de dépenses avec budget défini apparaît bien dans categoriesTotal.
  • inTarget compte correctement les catégories où les dépenses réelles ≤ budget (en absolu).
  • Les worstOverruns utilisent des montants absolus pour le calcul d'overrunAbs et d'overrunPct.
  • Test vitest de régression avec une fixture réaliste (budget expense négatif).
  • CHANGELOG.md + CHANGELOG.fr.md sous [Unreleased] > Fixed / Corrigé.
  • npm run build et npm test -- --run passent.

Complexité

Simple (un seul fichier + un test), mais important — la carte est cassée en production.

## Contexte Sur `/reports/cartes`, la carte "Adhésion budgétaire" (Budget Adherence) affiche systématiquement **« aucune catégorie avec budget ce mois-ci »** même quand l'utilisateur a des budgets définis sur des catégories de dépenses. ## Cause racine Dans `src/services/reportService.ts` (fonction `getCartesSnapshot`, ~ligne 912) : ```ts const budgetedExpenseRows = budgetRows.filter( (r) => r.category_type === "expense" && r.monthBudget > 0 && !r.is_parent, ); ``` Dans `src/services/budgetService.ts` ligne 260, `monthBudget` est **signé** : ```ts const signFor = (type: string) => (type === "expense" ? -1 : 1); const monthBudget = rawMonthBudget * sign; // négatif pour les expense ``` Le filtre `r.monthBudget > 0` est donc **toujours faux** pour les lignes `expense` → aucune catégorie ne passe, même avec un budget réel > 0. ## Autres filtres impactés - Ligne 916 : `Math.abs(r.monthActual) <= r.monthBudget` — compare `|actual|` à un `budget` négatif → toujours faux. - Ligne 922–923 : `overrunAbs = actual - r.monthBudget`, `overrunPct = overrunAbs / r.monthBudget` — calculs bruités par les signes. - Ligne 285 de `budgetService.ts` (hors scope de ce bug) utilise déjà `Math.abs(monthBudget)` pour le variation % — c'est la bonne approche à dupliquer. ## Fix proposé Dans `reportService.ts`, traiter les montants en absolu dans la carte Budget Adherence : ```ts const budgetedExpenseRows = budgetRows.filter( (r) => r.category_type === "expense" && Math.abs(r.monthBudget) > 0 && !r.is_parent, ); const budgetsInTarget = budgetedExpenseRows.filter( (r) => Math.abs(r.monthActual) <= Math.abs(r.monthBudget), ).length; const overruns = budgetedExpenseRows .map((r) => { const actual = Math.abs(r.monthActual); const budget = Math.abs(r.monthBudget); const overrunAbs = actual - budget; const overrunPct = budget > 0 ? (overrunAbs / budget) * 100 : null; return { ...r, actual, budget, overrunAbs, overrunPct }; }) .filter((r) => r.overrunAbs > 0) // ...tri existant ; ``` ## Fichiers concernés - `src/services/reportService.ts` (lignes ~912–942) - `src/services/reportService.cartes.test.ts` — ajouter un test de régression avec une catégorie `expense` ayant `monthBudget = -100` et `monthActual = -80`, qui doit passer le filtre (dans le fix) et compter comme "in target". ## Critères d'acceptation - [ ] Une catégorie de dépenses avec budget défini apparaît bien dans `categoriesTotal`. - [ ] `inTarget` compte correctement les catégories où les dépenses réelles ≤ budget (en absolu). - [ ] Les `worstOverruns` utilisent des montants absolus pour le calcul d'`overrunAbs` et d'`overrunPct`. - [ ] Test vitest de régression avec une fixture réaliste (budget expense négatif). - [ ] `CHANGELOG.md` + `CHANGELOG.fr.md` sous `[Unreleased]` > `Fixed` / `Corrigé`. - [ ] `npm run build` et `npm test -- --run` passent. ## Complexité Simple (un seul fichier + un test), mais important — la carte est cassée en production.
maximus added this to the spec-refonte-rapports milestone 2026-04-19 13:28:59 +00:00
maximus added the
status:ready
type:bug
source:human
labels 2026-04-19 13:28:59 +00:00
maximus added
status:review
and removed
status:ready
labels 2026-04-19 13:35:54 +00:00
maximus added
status:approved
and removed
status:review
labels 2026-04-19 13:39:13 +00:00
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#112
No description provided.