feat(reports): hierarchical real-vs-real compare with subtotals (#247) #249
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#249
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-247-compare-hierarchique"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Renders the real-vs-real comparable report as a parent/child category tree with subtotal rows (net per group), mirroring the budget report — expense/income/transfer sections, per-section + grand net totals, subtotals-on-top/bottom toggle. The service builds the tree on top of the flat per-category deltas; leaf values are unchanged and the #243 transfer netting is preserved (balanced transfer groups subtotal to ~0).
Stacked on #248 (issue #243). Review vs base issue-243-netting-transferts.
Resolves #247
Generated autonomously by /autopilot run of 2026-07-04
Revue /pr-review — APPROVE
Résumé. L'implémentation transforme le rapport comparable réel-vs-réel en arbre parent/enfant avec sous-totaux, en miroir fidèle du rapport de budget déjà en production (
BudgetVsActualTable). Le netting des transferts (#243) est préservé : les valeurs des feuilles sont inchangées et un sous-total = somme arithmétique des feuilles descendantes = net du groupe. Bonne couverture de tests, i18n complet FR/EN, CHANGELOG mis à jour dans les deux fichiers, aucune migration ni schéma touché.Points vérifiés
buildCompareTree: sous-total = somme des feuilles descendantes (pas de double-comptage multi-niveaux cardescendantLeavesfiltreis_parent), garde anti-cycle viaMAX_TREE_DEPTH, tri final stable par type (sections contiguës).ComparePeriodChartetgetCartesSnapshotfiltrentis_parent→ pas de double-comptage des sous-totaux contre leurs propres feuilles.reorderRows: contrainte générique assouplie aux 2 seuls champs lus (is_parent,depth) — compatible budget (champs requis) et compare (optionnels) ; l'algorithme ne lit niparent_idnicategory_id, l'assouplissement est sûr.type» est préexistante et déjà en prod dansBudgetVsActualTable(même sectionnement contigu par type) — #247 n'introduit aucun risque neuf.reports.subtotalsOnTop/Bottom,reports.compare.totalRow,reports.bva.*) présentes ; nouvelles clés ajoutées FR + EN.buildCompareTree+ test de câblage + assertion call-count passée à 2 requêtes).Suggestions (non bloquantes)
buildCompareTree— feuille àparent_idorphelin. Une feuille dont leparent_id(non-null) pointe vers un id absent du jeu de catégories n'est ni une racine (son parent manquant est marqué « relevant » pendant la remontée d'ancêtres) ni rattachée à un parent réel (aucune entrée d'adjacence) → elle est silencieusement omise du rapport. Quasi impossible en pratique (ON DELETE SET NULL+ soft-delete :COMPARE_CATEGORIES_SQLrécupère aussi les inactives), donc seulement sous corruption FK-off + hard-delete. Un filet défensif (traiter ce cas comme un orphelin, à côté du cascategoryId == null) fermerait le trou.Dénominateur du % — incohérence cosmétique. Les lignes sous-total (
is_parent) calculentdeltaPctavec un dénominateur signé (pour coller à la formule feuille derowsToDeltas), alors que les totaux de section et le grand total utilisentMath.abs(previous)via le helperpct(). Divergence uniquement quand le net précédent d'un groupe est négatif — en pratique les groupes de transferts, qui s'annulent à ~0 (donc % quasi nul et souventnull). Pattern préexistant : l'ancien grand total utilisait déjàabs.Commentaire périmé dans
getCartesSnapshot(l.950). «momRowsare sorted by absolute delta already » : après #247,momRowsest trié par type, plus par delta absolu. Sans impact fonctionnel (le code des top movers re-trie explicitement via.sort((a,b) => b.deltaAbs - a.deltaAbs)), mais le commentaire induit en erreur. Par ailleurs l'affirmation « byte-identical to the previous flat output » est légèrement inexacte : un parent ayant à la fois des enfants ET ses propres transactions directes fait apparaître sa dépense directe en feuille renommée « X (direct) » → un top-mover Cartes pourrait passer du libellé « X » à « X (direct) » (valeur inchangée). Rare (parent avec dépense directe) et purement cosmétique.Note process (PR stackée). La PR cible
issue-243-netting-transferts, pasmain→check.ymln'a jamais tourné dessus (les maillons intermédiaires d'une pile affichentno-status). Avant de merger la pile, valider le tip cumulé en local (tsc && vite build,vitest run,cargo check,cargo test). Revue statique : les tests sont cohérents avec l'implémentation, mais leur exécution reste à confirmer au merge.Revue adversariale automatisée via
/pr-review.Merged into
mainlocally as part of the milestone stack (merge commit8bd5121). Closing manually — the merge was done outside Forgejo's API, so it can't flag this PR as merged. The change is onmain.Pull request closed