Catégories orphelines : analyser et rattacher les catégories hors de la hiérarchie #393

Open
opened 2026-10-07 00:09:06 +00:00 by maximus · 1 comment
Owner

Contexte

Lors d'un test, une catégorie a été « perdue » : elle n'apparaissait plus dans l'arbre de la page Catégories après une manipulation non identifiée. Analyse du 2026-10-06 (/analyze, lu sur main 8907eef) : la cause la plus probable est un cycle de parenté créé depuis le formulaire d'édition, et rien dans l'app ne permet de le voir ni de le réparer.

Diagnostic vérifié dans le code

  • Le cycle se crée en un seul geste. Le sélecteur « Parent » de CategoryForm.tsx:44-62 propose toutes les catégories de niveau 0 et 1, y compris la catégorie éditée (auto-boucle) et ses propres enfants (A -> B -> A). updateCategory (categoryService.ts:80-95) écrit parent_id sans contrôle de cycle ni de profondeur (createCategory contrôle la profondeur, pas updateCategory : une catégorie de niveau 1 avec enfants rattachée sous une autre de niveau 1 donne 4 niveaux).
  • Un cycle masque la catégorie et toute sa descendance partout où l'on part des racines : arbre de la page Catégories (buildTree, useCategories.ts:61-78), sélecteurs de catégorie des transactions, du filtre et de la ventilation (sortHierarchical, CategoryCombobox.tsx:61-110), budget (useBudget.ts:139-239, budgetService.ts:258-389), rapports hiérarchiques (buildCategoryTree, reportService.ts:580-672 : montants absents).
  • Parent désactivé : la catégorie remonte en racine dans l'arbre et les sélecteurs, mais elle est masquée dans le budget (la construction part de !c.parent_id, et le parent est absent de la liste filtrée is_active = 1).
  • Les données sont intactes : seul parent_id est en cause. Transactions, mots-clés et budget_entries existent ; le tableau de bord (dashboardService.ts, LEFT JOIN sans hiérarchie) les compte encore. Remettre parent_id à une valeur valide fait tout réapparaître.
  • Boucle infinie latente : getCategoryDepth (categoryService.ts:30-44) remonte les parents dans un while sans garde ; avec un cycle actif, createCategory sous un membre du cycle ne se terminerait jamais (non atteignable par l'UI aujourd'hui, les membres d'un cycle n'étant jamais proposés comme parents).
  • Glisser-déposer sûr : moveCategory (useCategories.ts:260-327) ne peut pas déposer dans son propre sous-arbre (findNode échoue, return).
  • Autres chemins d'écriture : restoreCategories (dataExportService.ts:439) réinsère tels quels les parent_id d'une archive, cycles compris (hors portée, voir plus bas) ; deactivateCategory remonte les enfants d'un niveau (raccourcit un cycle existant, n'en crée pas) ; migration de catégories et reinitializeCategories sans risque.

Décisions (Max, 2026-10-06)

  • D1 — Détection et correction : un outil dans la page Catégories qui dénombre les catégories orphelines et permet de les corriger. Pas de bandeau automatique, pas de réparation silencieuse.
  • D2 — Portée : cycles + prévention + budget. La restauration d'archive reste hors portée.

Définition retenue d'une catégorie orpheline : catégorie active (is_active = 1) qui (a) appartient à un cycle ou en descend, (b) a un parent désactivé, ou (c) a un parent inexistant. Le cas (c) est probablement rendu impossible par les clés étrangères de sqlx (actives par défaut, non vérifié) mais reste détecté par sécurité.

Travail à faire

  • Service de diagnostic (categoryService.ts ou module pur dédié, testable sans base) : à partir des lignes id, parent_id, is_active, retourner les orphelines avec leur motif (cycle, inactive_parent, missing_parent). Ne retourner que les racines de chaque groupe à rattacher (pour un cycle : le ou les membres dont le rattachement suffit à le casser), la descendance suivant d'elle-même.
  • Service de rattachement : reattachCategory(id, parentId | null) qui passe par la même validation que updateCategory (point suivant). Écriture sous withTransaction si plusieurs lignes.
  • Validation dans updateCategory : refuser un parent_id égal à l'id, ou appartenant à la descendance de la catégorie (cycle), ou qui porterait la profondeur au-delà de 3 niveaux en comptant la descendance. Erreur nommée, traduite côté UI.
  • Garde dans getCategoryDepth : ensemble des ids visités, arrêt sur un cycle.
  • Sélecteur de parent (CategoryForm.tsx) : exclure la catégorie éditée et toute sa descendance, et les parents qui dépasseraient la profondeur.
  • UI dans la page Catégories : bouton « Vérifier la hiérarchie » qui affiche le nombre de catégories orphelines (ou « aucune »), et pour chacune son nom, son motif et un choix de nouveau parent (racine par défaut, sinon un parent valide). Rattacher recharge l'arbre. Événement écrit au Journal (logInfo) avec le nombre corrigé.
  • Budget : afficher une catégorie active dont le parent est désactivé ou absent (en racine, comme l'arbre et les sélecteurs), dans useBudget.ts et budgetService.ts.
  • i18n FR/EN, CHANGELOG.md + CHANGELOG.fr.md (Fixed + Added), section Catégories de docs/guide-utilisateur.md et clés docs.*.

Fichiers concernés

  • src/services/categoryService.ts — validation updateCategory, garde getCategoryDepth, diagnostic et rattachement
  • src/components/categories/CategoryForm.tsx — filtrage des options de parent
  • src/hooks/useCategories.ts — actions diagnostic/rattachement dans le reducer
  • src/pages/CategoriesPage.tsx (ou équivalent) + nouveau composant components/categories/ — outil de vérification
  • src/hooks/useBudget.ts, src/services/budgetService.ts — catégories à parent désactivé
  • src/i18n/locales/fr.json, en.json, CHANGELOG*.md, docs/guide-utilisateur.md

Surface de test

  • Tests existants : aucun pour categoryService, useCategories ni CategoryForm. CategoryCombobox.test.ts couvre les orphelins (:91) mais pas les cycles. useBudget.test.ts existe.
  • À ajouter :
    • diagnostic pur : auto-boucle, cycle à 2 et 3 membres avec descendance, parent désactivé, parent inexistant, arbre sain (aucune orpheline) ;
    • validation : parent = soi, parent = enfant, parent = petit-enfant, profondeur 4 refusée, déplacement valide accepté ;
    • getCategoryDepth se termine sur un cycle ;
    • options du sélecteur de parent sans la catégorie ni ses descendants (fonction pure extraite ou rendu renderToStaticMarkup, modèle LicenseCard.render.test.tsx) ;
    • budget : une catégorie à parent désactivé apparaît ;
    • CategoryCombobox : comportement sur cycle documenté par un test.
  • Test de régression requis : oui (type:bug) — reproduire A -> B -> A par updateCategory, constater le refus ; et sur des lignes déjà en cycle, constater que le diagnostic les trouve et que le rattachement les rend visibles dans buildTree.

Critères d'acceptation

  • Depuis la page Catégories, un bouton dénombre les catégories orphelines et indique leur motif ; un arbre sain affiche « aucune ».
  • Chaque orpheline peut être rattachée à la racine ou à un parent valide ; après rattachement elle réapparaît dans l'arbre, les sélecteurs, le budget et les rapports, avec ses transactions, mots-clés et montants de budget.
  • Le sélecteur « Parent » ne propose plus la catégorie éditée ni ses descendants.
  • updateCategory refuse un cycle et une profondeur supérieure à 3, avec un message traduit.
  • getCategoryDepth se termine sur un cycle.
  • Le budget affiche une catégorie dont le parent est désactivé.
  • FR/EN, CHANGELOG bilingue, guide utilisateur à jour ; aucune migration DB.

Hors portée

  • Contrôle des cycles à la restauration d'une archive (restoreCategories, dataExportService.ts:439) : l'outil de la page Catégories répare après coup ; issue séparée si besoin.
  • Bandeau ou réparation automatique au chargement (écartés par D1).

Complexité estimée

Medium.


Plan d'exécution — milestone lot-v018 (planifié le 2026-10-16)

Les sections ci-dessous viennent de la séance /plan-run du 2026-10-16 (revue /review-spec Sécurité, Architecture, Technique comprise). En cas de conflit avec le corps au-dessus, elles priment.

Consignes communes

  • Préflight : #369, #419 et #421 sont fermées et le tag v0.17.2 existe (git ls-remote --tags origin v0.17.2). Sinon, arrêt sans rien écrire.
  • Les numéros de ligne du corps ont été lus sur main 065f580 ; #369, #419 et #421 les ont déplacés depuis. Repérer chaque site par son contenu (symbole, chaîne), jamais par son numéro.
  • Toute clé i18n ajoutée l'est en FR et en EN (garde de #453, en tête de pile).
  • Les vérifications manuelles (app lancée) vont dans le corps de la PR et le DAILY-REPORT, pas dans STATE.md : le report dans STATE.md se fait au /merge-stack.

Depends on

Note : dépendance de code. La modale de rattachement utilise CategoryCombobox, dont la liste doit sortir de la modale (portail de #429).

Décisions prises en planification

  • La case « Sélecteur de parent (CategoryForm.tsx) » est hors portée : #423 l'a faite (src/components/categories/parentOptions.ts, commentaire 23053).
  • Module pur unique : extraire depthsOf et subtreeHeight de parentOptions.ts dans src/utils/categoryHierarchy.ts, avec un ensemble de visités sur toute remontée et toute descente. buildParentOptions, le diagnostic et la validation de service l'importent ; pas de troisième implémentation. parentOptions.test.ts reste vert.
  • Lecture des lignes : le diagnostic lit toutes les catégories (actives et inactives) par une requête dédiée SELECT id, parent_id, is_active FROM categories. Les requêtes existantes filtrent is_active = 1, ce qui ferait lire un parent désactivé comme missing_parent. La validation de profondeur ne compte que les catégories actives, comme l'arbre.
  • Cycle : le membre proposé au rattachement est celui qui a le plus petit id.
  • Erreur nommée : classe CategoryHierarchyError avec code: "self_parent" | "cycle" | "too_deep" | "parent_has_transactions", sur le modèle de TransactionLinkedToBalanceError. useCategories la reconnaît par instanceof et dispatche la clé i18n du code, au lieu de e.message.
  • Parent avec transactions directes (Max) : updateCategory et reattachCategory refusent un nouveau parent qui porte des transactions directes (code: "parent_has_transactions"). Sinon, dans la même transaction, le nouveau parent passe à is_inputable = 0, comme dans createCategory. L'UI n'exclut pas ces parents des listes : l'erreur traduite s'affiche à l'enregistrement, dans le formulaire de catégorie comme dans la modale.
  • Atomicité : la validation (lectures) et les écritures (parent_id, is_inputable du parent) se font sous un seul withTransaction, même pour une seule ligne.
  • Glisser-déposer (Max) : hors portée. updateCategorySortOrders écrit parent_id sans validation de service ; ce chemin reste gardé par l'UI (moveCategory refuse son propre sous-arbre). L'écrire dans la section « Hors portée » de la PR et dans docs/architecture.md.
  • UI (Max) : bouton « Vérifier la hiérarchie » dans l'en-tête de la page Catégories, entre « Tous les mots-clés » et « Réinitialiser », même style que « Réinitialiser ». Il ouvre une modale : nombre d'orphelines ou « aucune », une ligne par orpheline (nom, motif traduit, CategoryCombobox du nouveau parent alimenté par buildParentOptions, avec une option « Racine » en extraOptions présélectionnée, bouton « Rattacher »), et un bouton « Fermer ». Après un rattachement, la liste et l'arbre se rechargent, et une ligne logInfo donne le nombre corrigé.

Review caveats

  • (Architecture, critique résolue) Règle de profondeur dupliquée : résolu par le module pur unique.
  • (Architecture, critique résolue) inactive_parent indiscernable : résolu par la requête dédiée.
  • (Sécurité, retenu) Parcours non bornés sur des données cycliques : ensemble de visités obligatoire. Test : reattachCategory sur un membre d'un cycle à 2, puis à 3, se termine et casse le cycle.
  • (Sécurité, LOW, hors portée) restoreCategories peut réinjecter des cycles depuis une archive : l'outil répare après coup ; pas de logWarn ajouté ici.

Spec source

spec-plan-lot-v018.md

## Contexte Lors d'un test, une catégorie a été « perdue » : elle n'apparaissait plus dans l'arbre de la page Catégories après une manipulation non identifiée. Analyse du 2026-10-06 (`/analyze`, lu sur `main` `8907eef`) : la cause la plus probable est un **cycle de parenté** créé depuis le formulaire d'édition, et rien dans l'app ne permet de le voir ni de le réparer. ### Diagnostic vérifié dans le code - **Le cycle se crée en un seul geste.** Le sélecteur « Parent » de `CategoryForm.tsx:44-62` propose toutes les catégories de niveau 0 et 1, y compris la catégorie éditée (auto-boucle) et ses propres enfants (A -> B -> A). `updateCategory` (`categoryService.ts:80-95`) écrit `parent_id` sans contrôle de cycle ni de profondeur (`createCategory` contrôle la profondeur, pas `updateCategory` : une catégorie de niveau 1 avec enfants rattachée sous une autre de niveau 1 donne 4 niveaux). - **Un cycle masque la catégorie et toute sa descendance partout où l'on part des racines** : arbre de la page Catégories (`buildTree`, `useCategories.ts:61-78`), sélecteurs de catégorie des transactions, du filtre et de la ventilation (`sortHierarchical`, `CategoryCombobox.tsx:61-110`), budget (`useBudget.ts:139-239`, `budgetService.ts:258-389`), rapports hiérarchiques (`buildCategoryTree`, `reportService.ts:580-672` : montants absents). - **Parent désactivé** : la catégorie remonte en racine dans l'arbre et les sélecteurs, mais elle est **masquée dans le budget** (la construction part de `!c.parent_id`, et le parent est absent de la liste filtrée `is_active = 1`). - **Les données sont intactes** : seul `parent_id` est en cause. Transactions, mots-clés et `budget_entries` existent ; le tableau de bord (`dashboardService.ts`, `LEFT JOIN` sans hiérarchie) les compte encore. Remettre `parent_id` à une valeur valide fait tout réapparaître. - **Boucle infinie latente** : `getCategoryDepth` (`categoryService.ts:30-44`) remonte les parents dans un `while` sans garde ; avec un cycle actif, `createCategory` sous un membre du cycle ne se terminerait jamais (non atteignable par l'UI aujourd'hui, les membres d'un cycle n'étant jamais proposés comme parents). - **Glisser-déposer sûr** : `moveCategory` (`useCategories.ts:260-327`) ne peut pas déposer dans son propre sous-arbre (`findNode` échoue, `return`). - Autres chemins d'écriture : `restoreCategories` (`dataExportService.ts:439`) réinsère tels quels les `parent_id` d'une archive, cycles compris (hors portée, voir plus bas) ; `deactivateCategory` remonte les enfants d'un niveau (raccourcit un cycle existant, n'en crée pas) ; migration de catégories et `reinitializeCategories` sans risque. ## Décisions (Max, 2026-10-06) - **D1 — Détection et correction** : un **outil dans la page Catégories** qui dénombre les catégories orphelines et permet de les corriger. Pas de bandeau automatique, pas de réparation silencieuse. - **D2 — Portée** : cycles + prévention + budget. La restauration d'archive reste hors portée. Définition retenue d'une catégorie **orpheline** : catégorie active (`is_active = 1`) qui (a) appartient à un cycle ou en descend, (b) a un parent désactivé, ou (c) a un parent inexistant. Le cas (c) est probablement rendu impossible par les clés étrangères de sqlx (actives par défaut, non vérifié) mais reste détecté par sécurité. ## Travail à faire - [ ] **Service de diagnostic** (`categoryService.ts` ou module pur dédié, testable sans base) : à partir des lignes `id, parent_id, is_active`, retourner les orphelines avec leur motif (`cycle`, `inactive_parent`, `missing_parent`). Ne retourner que les racines de chaque groupe à rattacher (pour un cycle : le ou les membres dont le rattachement suffit à le casser), la descendance suivant d'elle-même. - [ ] **Service de rattachement** : `reattachCategory(id, parentId | null)` qui passe par la même validation que `updateCategory` (point suivant). Écriture sous `withTransaction` si plusieurs lignes. - [ ] **Validation dans `updateCategory`** : refuser un `parent_id` égal à l'id, ou appartenant à la descendance de la catégorie (cycle), ou qui porterait la profondeur au-delà de 3 niveaux en comptant la descendance. Erreur nommée, traduite côté UI. - [ ] **Garde dans `getCategoryDepth`** : ensemble des ids visités, arrêt sur un cycle. - [ ] **Sélecteur de parent** (`CategoryForm.tsx`) : exclure la catégorie éditée et toute sa descendance, et les parents qui dépasseraient la profondeur. - [ ] **UI dans la page Catégories** : bouton « Vérifier la hiérarchie » qui affiche le nombre de catégories orphelines (ou « aucune »), et pour chacune son nom, son motif et un choix de nouveau parent (racine par défaut, sinon un parent valide). Rattacher recharge l'arbre. Événement écrit au Journal (`logInfo`) avec le nombre corrigé. - [ ] **Budget** : afficher une catégorie active dont le parent est désactivé ou absent (en racine, comme l'arbre et les sélecteurs), dans `useBudget.ts` et `budgetService.ts`. - [ ] i18n FR/EN, `CHANGELOG.md` + `CHANGELOG.fr.md` (Fixed + Added), section Catégories de `docs/guide-utilisateur.md` et clés `docs.*`. ## Fichiers concernés - `src/services/categoryService.ts` — validation `updateCategory`, garde `getCategoryDepth`, diagnostic et rattachement - `src/components/categories/CategoryForm.tsx` — filtrage des options de parent - `src/hooks/useCategories.ts` — actions diagnostic/rattachement dans le reducer - `src/pages/CategoriesPage.tsx` (ou équivalent) + nouveau composant `components/categories/` — outil de vérification - `src/hooks/useBudget.ts`, `src/services/budgetService.ts` — catégories à parent désactivé - `src/i18n/locales/fr.json`, `en.json`, `CHANGELOG*.md`, `docs/guide-utilisateur.md` ## Surface de test - Tests existants : aucun pour `categoryService`, `useCategories` ni `CategoryForm`. `CategoryCombobox.test.ts` couvre les orphelins (`:91`) mais pas les cycles. `useBudget.test.ts` existe. - À ajouter : - diagnostic pur : auto-boucle, cycle à 2 et 3 membres avec descendance, parent désactivé, parent inexistant, arbre sain (aucune orpheline) ; - validation : parent = soi, parent = enfant, parent = petit-enfant, profondeur 4 refusée, déplacement valide accepté ; - `getCategoryDepth` se termine sur un cycle ; - options du sélecteur de parent sans la catégorie ni ses descendants (fonction pure extraite ou rendu `renderToStaticMarkup`, modèle `LicenseCard.render.test.tsx`) ; - budget : une catégorie à parent désactivé apparaît ; - `CategoryCombobox` : comportement sur cycle documenté par un test. - Test de régression requis : **oui** (`type:bug`) — reproduire A -> B -> A par `updateCategory`, constater le refus ; et sur des lignes déjà en cycle, constater que le diagnostic les trouve et que le rattachement les rend visibles dans `buildTree`. ## Critères d'acceptation - [ ] Depuis la page Catégories, un bouton dénombre les catégories orphelines et indique leur motif ; un arbre sain affiche « aucune ». - [ ] Chaque orpheline peut être rattachée à la racine ou à un parent valide ; après rattachement elle réapparaît dans l'arbre, les sélecteurs, le budget et les rapports, avec ses transactions, mots-clés et montants de budget. - [ ] Le sélecteur « Parent » ne propose plus la catégorie éditée ni ses descendants. - [ ] `updateCategory` refuse un cycle et une profondeur supérieure à 3, avec un message traduit. - [ ] `getCategoryDepth` se termine sur un cycle. - [ ] Le budget affiche une catégorie dont le parent est désactivé. - [ ] FR/EN, CHANGELOG bilingue, guide utilisateur à jour ; aucune migration DB. ## Hors portée - Contrôle des cycles à la restauration d'une archive (`restoreCategories`, `dataExportService.ts:439`) : l'outil de la page Catégories répare après coup ; issue séparée si besoin. - Bandeau ou réparation automatique au chargement (écartés par D1). ## Complexité estimée Medium. --- # Plan d'exécution — milestone lot-v018 (planifié le 2026-10-16) Les sections ci-dessous viennent de la séance `/plan-run` du 2026-10-16 (revue `/review-spec` Sécurité, Architecture, Technique comprise). **En cas de conflit avec le corps au-dessus, elles priment.** ## Consignes communes - Préflight : #369, #419 et #421 sont fermées et le tag `v0.17.2` existe (`git ls-remote --tags origin v0.17.2`). Sinon, arrêt sans rien écrire. - Les numéros de ligne du corps ont été lus sur `main` `065f580` ; #369, #419 et #421 les ont déplacés depuis. Repérer chaque site par son contenu (symbole, chaîne), jamais par son numéro. - Toute clé i18n ajoutée l'est en FR et en EN (garde de #453, en tête de pile). - Les vérifications manuelles (app lancée) vont dans le corps de la PR et le DAILY-REPORT, pas dans `STATE.md` : le report dans `STATE.md` se fait au `/merge-stack`. ## Depends on - #429 Note : dépendance de code. La modale de rattachement utilise `CategoryCombobox`, dont la liste doit sortir de la modale (portail de #429). ## Décisions prises en planification - **La case « Sélecteur de parent (`CategoryForm.tsx`) » est hors portée** : #423 l'a faite (`src/components/categories/parentOptions.ts`, commentaire 23053). - **Module pur unique** : extraire `depthsOf` et `subtreeHeight` de `parentOptions.ts` dans `src/utils/categoryHierarchy.ts`, avec un ensemble de visités sur toute remontée et toute descente. `buildParentOptions`, le diagnostic et la validation de service l'importent ; pas de troisième implémentation. `parentOptions.test.ts` reste vert. - **Lecture des lignes** : le diagnostic lit toutes les catégories (actives et inactives) par une requête dédiée `SELECT id, parent_id, is_active FROM categories`. Les requêtes existantes filtrent `is_active = 1`, ce qui ferait lire un parent désactivé comme `missing_parent`. La validation de profondeur ne compte que les catégories actives, comme l'arbre. - **Cycle** : le membre proposé au rattachement est celui qui a le plus petit `id`. - **Erreur nommée** : classe `CategoryHierarchyError` avec `code: "self_parent" | "cycle" | "too_deep" | "parent_has_transactions"`, sur le modèle de `TransactionLinkedToBalanceError`. `useCategories` la reconnaît par `instanceof` et dispatche la clé i18n du code, au lieu de `e.message`. - **Parent avec transactions directes** (Max) : `updateCategory` et `reattachCategory` refusent un nouveau parent qui porte des transactions directes (`code: "parent_has_transactions"`). Sinon, dans la même transaction, le nouveau parent passe à `is_inputable = 0`, comme dans `createCategory`. L'UI n'exclut pas ces parents des listes : l'erreur traduite s'affiche à l'enregistrement, dans le formulaire de catégorie comme dans la modale. - **Atomicité** : la validation (lectures) et les écritures (`parent_id`, `is_inputable` du parent) se font sous un seul `withTransaction`, même pour une seule ligne. - **Glisser-déposer** (Max) : hors portée. `updateCategorySortOrders` écrit `parent_id` sans validation de service ; ce chemin reste gardé par l'UI (`moveCategory` refuse son propre sous-arbre). L'écrire dans la section « Hors portée » de la PR et dans `docs/architecture.md`. - **UI** (Max) : bouton « Vérifier la hiérarchie » dans l'en-tête de la page Catégories, entre « Tous les mots-clés » et « Réinitialiser », même style que « Réinitialiser ». Il ouvre une modale : nombre d'orphelines ou « aucune », une ligne par orpheline (nom, motif traduit, `CategoryCombobox` du nouveau parent alimenté par `buildParentOptions`, avec une option « Racine » en `extraOptions` présélectionnée, bouton « Rattacher »), et un bouton « Fermer ». Après un rattachement, la liste et l'arbre se rechargent, et une ligne `logInfo` donne le nombre corrigé. ## Review caveats - (Architecture, critique résolue) Règle de profondeur dupliquée : résolu par le module pur unique. - (Architecture, critique résolue) `inactive_parent` indiscernable : résolu par la requête dédiée. - (Sécurité, retenu) Parcours non bornés sur des données cycliques : ensemble de visités obligatoire. Test : `reattachCategory` sur un membre d'un cycle à 2, puis à 3, se termine et casse le cycle. - (Sécurité, LOW, hors portée) `restoreCategories` peut réinjecter des cycles depuis une archive : l'outil répare après coup ; pas de `logWarn` ajouté ici. ## Spec source spec-plan-lot-v018.md
maximus added the
status:triage
type:bug
source:human
labels 2026-10-07 00:09:06 +00:00
maximus added
status:ready
and removed
status:triage
labels 2026-10-07 00:13:41 +00:00
Author
Owner

Note du /analyze de #423 (2026-10-12) : la tâche « Sélecteur de parent (CategoryForm.tsx) : exclure la catégorie éditée et toute sa descendance, et les parents qui dépasseraient la profondeur » est reprise par #423 (décision D1 de Max), qui remplace ce sélecteur par CategoryCombobox. #393 garde la validation dans updateCategory, la garde de getCategoryDepth, l'outil « Vérifier la hiérarchie » et le budget.

Note du `/analyze` de #423 (2026-10-12) : la tâche « Sélecteur de parent (`CategoryForm.tsx`) : exclure la catégorie éditée et toute sa descendance, et les parents qui dépasseraient la profondeur » est reprise par **#423** (décision D1 de Max), qui remplace ce sélecteur par `CategoryCombobox`. #393 garde la validation dans `updateCategory`, la garde de `getCategoryDepth`, l'outil « Vérifier la hiérarchie » et le budget.
maximus added this to the planned-2026-10-16-lot-v018 milestone 2026-10-17 00:14:25 +00:00
maximus added
status:in-progress
and removed
status:ready
labels 2026-10-18 22:52:49 +00:00
maximus added
status:review
and removed
status:in-progress
labels 2026-10-18 23:27:33 +00:00
maximus added
status:approved
and removed
status:review
labels 2026-10-19 02:21:45 +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#393
No description provided.