feat(budget): flip grid to income-first + add Résultat rows #285

Closed
maximus wants to merge 0 commits from issue-278-budget-grid-income-first into main
Owner

Generated autonomously by /autopilot run of 2026-07-11

Aligns the budget grid (BudgetTable) on the income-statement standard already shipped for the compare/trend reports (epic #260).

Summary

  • Flipped TYPE_ORDER (useBudget.ts) and typeOrder (BudgetTable.tsx) to income-first (income → expense → transfer), replacing the previous expense-first ordering.
  • Added src/components/budget/budgetTableResults.ts — a new pure, tested module (computeBudgetResults) that rolls up the grid's leaves into Résultat avant transferts (income − expense) and Résultat net (+ transfers), across the previous-year-actual, budgeted-annual, and budgeted-monthly columns. Shape mirrors compareResults.ts (studied per the issue's guidance).
  • BudgetTable.tsx: the old unlabeled grand-"Total" row is replaced by the two Résultat rows, interleaved the same way as ComparePeriodTable/CategoryOverTimeTable (Résultat avant transferts sits between the expense and transfer sections, shown only when transfers exist; Résultat net is always the bottom line). Mathematically equivalent to the old total for every input — only the labeling/positioning changed.
  • Reused the existing reports.compare.resultBeforeTransfers / reports.compare.resultNet i18n keys (already FR/EN) rather than adding duplicates — no new strings needed.
  • CHANGELOG (EN+FR) entry under ## [Unreleased] / ### Changed.

Frozen decisions honored

  • No empty-lines toggle, no collapse — the grid keeps every category (it's an edit surface). The pre-existing subtotals-on-top/bottom toggle (reorderRows) is untouched and orthogonal (reorders, never hides).
  • Roll-up logic lives entirely in the new pure module, not inline in the .tsx.

Testing

  • Baseline on fresh branch: 791 vitest (47 files). After: 798 vitest (48 files) — 7 new tests in budgetTableResults.test.ts, zero regressions.
  • npm run build (tsc + vite) green.

Caveat for reviewer

  • None. Scope matched the issue's file list exactly (useBudget.ts, budgetTableResults.ts + test, BudgetTable.tsx); BudgetVsActualTable/budgetService.ts (sibling issue #277, BVA) and the dashboard (#279) were left untouched.

Resolves #278

Generated autonomously by /autopilot run of 2026-07-11 Aligns the budget grid (`BudgetTable`) on the income-statement standard already shipped for the compare/trend reports (epic #260). ## Summary - Flipped `TYPE_ORDER` (`useBudget.ts`) and `typeOrder` (`BudgetTable.tsx`) to income-first (income → expense → transfer), replacing the previous expense-first ordering. - Added `src/components/budget/budgetTableResults.ts` — a new pure, tested module (`computeBudgetResults`) that rolls up the grid's leaves into **Résultat avant transferts** (income − expense) and **Résultat net** (+ transfers), across the previous-year-actual, budgeted-annual, and budgeted-monthly columns. Shape mirrors `compareResults.ts` (studied per the issue's guidance). - `BudgetTable.tsx`: the old unlabeled grand-"Total" row is replaced by the two Résultat rows, interleaved the same way as `ComparePeriodTable`/`CategoryOverTimeTable` (Résultat avant transferts sits between the expense and transfer sections, shown only when transfers exist; Résultat net is always the bottom line). Mathematically equivalent to the old total for every input — only the labeling/positioning changed. - Reused the existing `reports.compare.resultBeforeTransfers` / `reports.compare.resultNet` i18n keys (already FR/EN) rather than adding duplicates — no new strings needed. - CHANGELOG (EN+FR) entry under `## [Unreleased]` / `### Changed`. ## Frozen decisions honored - **No** empty-lines toggle, **no** collapse — the grid keeps every category (it's an edit surface). The pre-existing subtotals-on-top/bottom toggle (`reorderRows`) is untouched and orthogonal (reorders, never hides). - Roll-up logic lives entirely in the new pure module, not inline in the `.tsx`. ## Testing - Baseline on fresh branch: 791 vitest (47 files). After: **798 vitest (48 files)** — 7 new tests in `budgetTableResults.test.ts`, zero regressions. - `npm run build` (tsc + vite) green. ## Caveat for reviewer - None. Scope matched the issue's file list exactly (`useBudget.ts`, `budgetTableResults.ts` + test, `BudgetTable.tsx`); `BudgetVsActualTable`/`budgetService.ts` (sibling issue #277, BVA) and the dashboard (#279) were left untouched. Resolves #278
maximus added 1 commit 2026-07-11 20:59:19 +00:00
feat(budget): flip grid to income-first + add Résultat rows
All checks were successful
PR Check / rust (pull_request) Successful in 21m4s
PR Check / frontend (pull_request) Successful in 2m20s
ab67c68605
Align BudgetTable on the income-statement standard shipped for the
compare/trend reports: sections now read Income -> Expense -> Transfer
(useBudget's TYPE_ORDER and BudgetTable's typeOrder both flipped), and
the previously unlabeled grand-total row is replaced by two interleaved
result rows (Result before transfers / Net result), computed by a new
pure, tested module (budgetTableResults.ts) mirroring compareResults.ts's
shape. Roll-up covers the previous-year-actual, budgeted-annual, and
budgeted-monthly columns alike.

All categories remain displayed (the grid stays a full edit surface) --
no collapse, no empty-row toggle, matching this issue's frozen decisions.

Resolves #278
maximus added the
autopilot:pending-human
label 2026-07-11 20:59:39 +00:00
Author
Owner

/pr-review — PR #285 (issue #278)

VERDICT : APPROVE

Flip income-first de la grille budget + lignes Résultat, propre et bien testé. Scope respecté à la lettre (useBudget / BudgetTable / nouveau module pur + test), aucun résidu expense-first, filtre accountIds M1 préservé, aucune migration.

Vérifications (toutes ✓)

  • Ordre income-first cohérentTYPE_ORDER (useBudget.ts:74) et typeOrder (BudgetTable.tsx:133) flippés en income→expense→transfer ; rendu piloté par des appels explicites renderTypeSection("income"/"expense"/"transfer"). Aucun common.total ni ordre expense-first résiduel sur la branche. Le TYPE_ORDER expense-first restant dans budgetService.ts:214 alimente le rapport BVA (#277), hors scope explicite de cette PR — pas un défaut ici.
  • Calcul des Résultats / signesresultBefore = income + expense (dépenses pré-signées négatives via signForBudgetType, donc income − |expense|), resultNet = resultBefore + transfer. previousYearTotal non re-signé (déjà signé en DB), conforme à l'ancien code. resultNet est mathématiquement équivalent à l'ancien grand-total pour toute entrée : category_type est strictement income|expense|transfer (shared/types/index.ts:145), donc aucun leaf n'est exclu du repli.
  • Sous-totaux sur lignes brutescomputeBudgetResults/sumLeavesForType filtrent is_parent → pas de double-comptage, pas de bug type-#254 (et la grille n'a de toute façon aucun repli ; toggle reorderRows/subtotals intact et orthogonal).
  • Câblage des réels + filtre comptesgetActualTotalsForYear(year-1, ids) propage accountIdsgetActualsByCategoryRange en SQL paramétrée (inPlaceholders + params.push, source_id IN (...) par placeholders, pas d'injection). Budgété non filtré par compte (correct, un budget n'est pas lié à un compte).
  • Tests — 7 nouveaux tests purs (budgetTableResults.test.ts), arithmétique vérifiée cas par cas (income−expense, transfert déséquilibré à une jambe, déficit, exclusion parent, pass-through prevYear non re-signé, helper de signe). Aucun .only/.skip. Build + 798 vitest annoncés verts.
  • i18n — clés reports.compare.resultBeforeTransfers / resultNet réutilisées (présentes FR et EN, l.446-447) ; en-têtes via budget.* existants. Zéro chaîne en dur.
  • CHANGELOG EN + FR sous [Unreleased] / Changed. Aucune migration DB touchée. Commit conventional, Resolves #278.

Suggestions (non bloquantes)

  1. typeOrder dans BudgetTable.tsx n'est plus lu qu'au niveau type ((typeof typeOrder)[number]) — l'ordre de rendu vient désormais des appels explicites, pas de l'itération du tableau. De même le volet cross-type de TYPE_ORDER (useBudget) est masqué par le re-groupage de la table. Les deux flips sont donc « belt-and-suspenders » (corrects, mais l'ordre visuel réel tient aux appels JSX). Rien à corriger — juste à garder en tête si un futur refactor s'appuie sur ces tableaux.
  2. Le gating de rendu (hasTransfers → insertion de resultBefore) n'est pas couvert par un test composant. La logique pure l'est ; conforme à la convention du repo (tests sur modules purs). OK de laisser tel quel.
  3. Réutiliser des clés reports.compare.* depuis un écran Budget crée un léger couplage de namespace ; acceptable vu les chaînes identiques et l'intention DRY.
## `/pr-review` — PR #285 (issue #278) **VERDICT : APPROVE** Flip income-first de la grille budget + lignes Résultat, propre et bien testé. Scope respecté à la lettre (useBudget / BudgetTable / nouveau module pur + test), aucun résidu expense-first, filtre `accountIds` M1 préservé, aucune migration. ### Vérifications (toutes ✓) - **Ordre income-first cohérent** — `TYPE_ORDER` (`useBudget.ts:74`) et `typeOrder` (`BudgetTable.tsx:133`) flippés en income→expense→transfer ; rendu piloté par des appels explicites `renderTypeSection("income"/"expense"/"transfer")`. Aucun `common.total` ni ordre expense-first résiduel sur la branche. Le `TYPE_ORDER` expense-first restant dans `budgetService.ts:214` alimente le rapport **BVA (#277)**, hors scope explicite de cette PR — pas un défaut ici. - **Calcul des Résultats / signes** — `resultBefore = income + expense` (dépenses pré-signées négatives via `signForBudgetType`, donc income − |expense|), `resultNet = resultBefore + transfer`. `previousYearTotal` **non** re-signé (déjà signé en DB), conforme à l'ancien code. `resultNet` est **mathématiquement équivalent à l'ancien grand-total** pour toute entrée : `category_type` est strictement `income|expense|transfer` (`shared/types/index.ts:145`), donc aucun leaf n'est exclu du repli. - **Sous-totaux sur lignes brutes** — `computeBudgetResults`/`sumLeavesForType` filtrent `is_parent` → pas de double-comptage, pas de bug type-#254 (et la grille n'a de toute façon aucun repli ; toggle `reorderRows`/subtotals intact et orthogonal). - **Câblage des réels + filtre comptes** — `getActualTotalsForYear(year-1, ids)` propage `accountIds` → `getActualsByCategoryRange` en **SQL paramétrée** (`inPlaceholders` + `params.push`, `source_id IN (...)` par placeholders, pas d'injection). Budgété non filtré par compte (correct, un budget n'est pas lié à un compte). - **Tests** — 7 nouveaux tests purs (`budgetTableResults.test.ts`), arithmétique vérifiée cas par cas (income−expense, transfert déséquilibré à une jambe, déficit, exclusion parent, pass-through prevYear non re-signé, helper de signe). Aucun `.only`/`.skip`. Build + 798 vitest annoncés verts. - **i18n** — clés `reports.compare.resultBeforeTransfers` / `resultNet` réutilisées (présentes FR **et** EN, l.446-447) ; en-têtes via `budget.*` existants. Zéro chaîne en dur. - **CHANGELOG** EN + FR sous `[Unreleased] / Changed`. Aucune migration DB touchée. Commit conventional, `Resolves #278`. ### Suggestions (non bloquantes) 1. `typeOrder` dans `BudgetTable.tsx` n'est plus lu qu'au **niveau type** (`(typeof typeOrder)[number]`) — l'ordre de rendu vient désormais des appels explicites, pas de l'itération du tableau. De même le volet cross-type de `TYPE_ORDER` (useBudget) est masqué par le re-groupage de la table. Les deux flips sont donc « belt-and-suspenders » (corrects, mais l'ordre visuel réel tient aux appels JSX). Rien à corriger — juste à garder en tête si un futur refactor s'appuie sur ces tableaux. 2. Le gating de rendu (`hasTransfers` → insertion de `resultBefore`) n'est pas couvert par un test composant. La logique pure l'est ; conforme à la convention du repo (tests sur modules purs). OK de laisser tel quel. 3. Réutiliser des clés `reports.compare.*` depuis un écran Budget crée un léger couplage de namespace ; acceptable vu les chaînes identiques et l'intention DRY.
maximus closed this pull request 2026-07-12 01:25:42 +00:00
Author
Owner

Mergée localement dans main dans le cadre de la pile M2 (a982f9e) — merge --no-ff + résolution des conflits CHANGELOG (union #277/#278/#279). Forgejo ne marque pas les merges locaux comme merged ; fermeture manuelle. Issue liée auto-fermée via Resolves #N. Tip cumulé validé : tsc + vite build, 811 vitest, cargo check + 98 tests Rust.

Mergée localement dans `main` dans le cadre de la pile M2 (`a982f9e`) — merge `--no-ff` + résolution des conflits CHANGELOG (union #277/#278/#279). Forgejo ne marque pas les merges locaux comme *merged* ; fermeture manuelle. Issue liée auto-fermée via `Resolves #N`. Tip cumulé validé : tsc + vite build, 811 vitest, cargo check + 98 tests Rust.
All checks were successful
PR Check / rust (pull_request) Successful in 21m4s
PR Check / frontend (pull_request) Successful in 2m20s

Pull request closed

Sign in to join this conversation.
No reviewers
No milestone
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#285
No description provided.