bug(onboarding): la case « Classer une transaction à la main » ne se coche jamais — le classement manuel écrit 'true' en texte #390

Closed
opened 2026-10-06 23:09:54 +00:00 by maximus · 2 comments
Owner

Constat

Max, le 2026-10-06, en testant l'onboarding phase 1 sur la v0.17.0 : classer une transaction à la main sur la page Transactions ne coche pas la case 5 de la liste de démarrage, « Classer une transaction à la main ».

Cause (vérifiée)

  • useTransactions.updateCategory appelle updateTransactionCategory(txId, categoryId, true) (src/hooks/useTransactions.ts:279). Le service lie ce booléen tel quel : is_manually_categorized = $2 avec [categoryId, isManual, txId] (src/services/transactionService.ts:296-306).
  • tauri-plugin-sql 2.3.2 ne traite à part que null, chaîne et nombre (src/wrapper.rs:155-165). Un booléen tombe dans query.bind(value) avec un serde_json::Value, que sqlx 0.8.6 encode en JSON, donc en texte : la colonne reçoit 'true', pas 1.
  • La colonne est is_manually_categorized INTEGER NOT NULL DEFAULT 0. L'affinité INTEGER ne convertit que les textes qui ont la forme d'un nombre, donc 'true' reste du texte.
  • Le compteur de la liste lit COUNT(*) FROM transactions WHERE is_manually_categorized = 1 (src/services/onboardingService.ts:43). Un texte n'est jamais égal à l'entier 1, donc le compteur reste à 0.

Reproduit sur SQLite 3.45, avec la même définition de colonne : la ligne écrite 'true' a typeof = text, celle écrite 1 a typeof = integer, et WHERE m = 1 ne compte que la seconde.

Aucun test ne touche updateTransactionCategory ni updateCategory. onboardingService.test.ts vérifie le texte de la requête contre un mock, et onboardingChecklist.test.ts part de compteurs déjà calculés.

Portée

  • Le chemin d'écriture date de la v0.1.0 (2b9fc49, 2026-02-08). Toute transaction classée depuis la page Transactions porte 'true' en texte, dans tous les profils existants.
  • Le défaut était invisible jusqu'à la liste de démarrage :
    • le seul autre filtre SQL sur cette colonne, autoCategorizeTransactions (= 0), écarte ces lignes, ce qui est le comportement voulu ;
    • côté JS, la chaîne "true" relue est vraie.
  • Effets de bord :
    • un export CSV puis réimport perd le drapeau (parseInt("true") || 0 vaut 0, dataExportService.ts:363) ;
    • une sauvegarde JSON réimportée réécrit "true" tel quel (dataExportService.ts:632-641).
  • C'est le seul site fautif. Les autres écritures de booléens convertissent déjà en 1/0 (categoryService.ts:282, balance.service.ts:229-230 et 494-501), et applyKeywordWithReassignment écrit 1 en dur (categorizationService.ts:266-272).

Correctif proposé

  1. Écrire un entier dans updateTransactionCategory : isManual ? 1 : 0.
  2. Ajouter une migration v18, sans modifier les existantes : UPDATE transactions SET is_manually_categorized = 1 WHERE is_manually_categorized = 'true'. Les profils existants retrouvent ainsi leur case 5 à la mise à jour, sans rien refaire.
  3. Normaliser la valeur à l'import d'une sauvegarde : une sauvegarde JSON antérieure porte "true".

Tests attendus

  • Le paramètre lié par updateTransactionCategory est l'entier 1. Remettre isManual brut doit faire rougir le test.
  • La migration v18 convertit 'true' en 1 et laisse 0 et 1 intacts.
  • L'import JSON d'une transaction "is_manually_categorized": "true" écrit 1.

Critères

  • Classer une transaction sur la page Transactions coche la case 5 au retour sur le tableau de bord.
  • Après la mise à jour, un profil qui a des classements manuels antérieurs voit la case 5 cochée.
  • Entrées « Fixed » / « Corrigé » dans CHANGELOG.md et CHANGELOG.fr.md.
## Constat Max, le 2026-10-06, en testant l'onboarding phase 1 sur la v0.17.0 : classer une transaction à la main sur la page Transactions ne coche pas la case 5 de la liste de démarrage, « Classer une transaction à la main ». ## Cause (vérifiée) - `useTransactions.updateCategory` appelle `updateTransactionCategory(txId, categoryId, true)` (`src/hooks/useTransactions.ts:279`). Le service lie ce booléen tel quel : `is_manually_categorized = $2` avec `[categoryId, isManual, txId]` (`src/services/transactionService.ts:296-306`). - tauri-plugin-sql 2.3.2 ne traite à part que null, chaîne et nombre (`src/wrapper.rs:155-165`). Un booléen tombe dans `query.bind(value)` avec un `serde_json::Value`, que sqlx 0.8.6 encode en JSON, donc en **texte** : la colonne reçoit `'true'`, pas `1`. - La colonne est `is_manually_categorized INTEGER NOT NULL DEFAULT 0`. L'affinité INTEGER ne convertit que les textes qui ont la forme d'un nombre, donc `'true'` reste du texte. - Le compteur de la liste lit `COUNT(*) FROM transactions WHERE is_manually_categorized = 1` (`src/services/onboardingService.ts:43`). Un texte n'est jamais égal à l'entier 1, donc le compteur reste à 0. Reproduit sur SQLite 3.45, avec la même définition de colonne : la ligne écrite `'true'` a `typeof` = `text`, celle écrite `1` a `typeof` = `integer`, et `WHERE m = 1` ne compte que la seconde. Aucun test ne touche `updateTransactionCategory` ni `updateCategory`. `onboardingService.test.ts` vérifie le texte de la requête contre un mock, et `onboardingChecklist.test.ts` part de compteurs déjà calculés. ## Portée - Le chemin d'écriture date de la v0.1.0 (`2b9fc49`, 2026-02-08). Toute transaction classée depuis la page Transactions porte `'true'` en texte, dans tous les profils existants. - Le défaut était invisible jusqu'à la liste de démarrage : - le seul autre filtre SQL sur cette colonne, `autoCategorizeTransactions` (`= 0`), écarte ces lignes, ce qui est le comportement voulu ; - côté JS, la chaîne `"true"` relue est vraie. - Effets de bord : - un export CSV puis réimport perd le drapeau (`parseInt("true") || 0` vaut 0, `dataExportService.ts:363`) ; - une sauvegarde JSON réimportée réécrit `"true"` tel quel (`dataExportService.ts:632-641`). - C'est le seul site fautif. Les autres écritures de booléens convertissent déjà en 1/0 (`categoryService.ts:282`, `balance.service.ts:229-230` et `494-501`), et `applyKeywordWithReassignment` écrit `1` en dur (`categorizationService.ts:266-272`). ## Correctif proposé 1. Écrire un entier dans `updateTransactionCategory` : `isManual ? 1 : 0`. 2. Ajouter une migration v18, sans modifier les existantes : `UPDATE transactions SET is_manually_categorized = 1 WHERE is_manually_categorized = 'true'`. Les profils existants retrouvent ainsi leur case 5 à la mise à jour, sans rien refaire. 3. Normaliser la valeur à l'import d'une sauvegarde : une sauvegarde JSON antérieure porte `"true"`. ## Tests attendus - Le paramètre lié par `updateTransactionCategory` est l'entier `1`. Remettre `isManual` brut doit faire rougir le test. - La migration v18 convertit `'true'` en 1 et laisse 0 et 1 intacts. - L'import JSON d'une transaction `"is_manually_categorized": "true"` écrit 1. ## Critères - [ ] Classer une transaction sur la page Transactions coche la case 5 au retour sur le tableau de bord. - [ ] Après la mise à jour, un profil qui a des classements manuels antérieurs voit la case 5 cochée. - [ ] Entrées « Fixed » / « Corrigé » dans `CHANGELOG.md` et `CHANGELOG.fr.md`.
maximus added the
status:ready
type:bug
source:human
labels 2026-10-06 23:09:54 +00:00
maximus added
status:review
and removed
status:ready
labels 2026-10-07 00:56:54 +00:00
maximus added
status:approved
and removed
status:review
labels 2026-10-07 01:41:00 +00:00
maximus added
status:review
and removed
status:approved
labels 2026-10-07 01:51:23 +00:00
maximus added
status:blocked
and removed
status:review
labels 2026-10-07 02:00:30 +00:00
Author
Owner

La PR #394 est mergée en avance rapide le 2026-10-06 (main = 5910387). L'issue reste ouverte, comme l'annonce Refs : elle attend les vérifications de la liste post-merge de la PR, à faire sur un build qui contient le correctif (version suivante ou build local) :

  1. Classer une transaction sur la page Transactions, puis revenir au tableau de bord : la case 5 doit être cochée.
  2. Sous Linux (Pop!_OS), ouvrir un vrai profil secondaire ancien :
    • le Journal doit dire « database updated with versions … » ;
    • la copie doit être dans ~/.config/com.simpl.resultat/migration-backups/ ;
    • l'import doit fonctionner dans ce profil.
  3. Sous Linux, supprimer un profil de test : son .db et ses fichiers annexes doivent disparaître de ~/.config/com.simpl.resultat/.
  4. Sous Linux, les colonnes de rendement du Bilan doivent être remplies.
  5. Un profil qui avait des classements manuels avant la mise à jour doit voir sa case 5 cochée.

L'issue passe en status:blocked jusqu'à ces vérifications ; elle sera fermée à la main, avec les preuves. Suivis non bloquants de la revue : #395.

La PR #394 est mergée en avance rapide le 2026-10-06 (`main` = `5910387`). L'issue reste ouverte, comme l'annonce `Refs` : elle attend les vérifications de la liste post-merge de la PR, à faire sur un build qui contient le correctif (version suivante ou build local) : 1. Classer une transaction sur la page Transactions, puis revenir au tableau de bord : la case 5 doit être cochée. 2. Sous Linux (Pop!_OS), ouvrir un vrai profil secondaire ancien : - le Journal doit dire « database updated with versions … » ; - la copie doit être dans `~/.config/com.simpl.resultat/migration-backups/` ; - l'import doit fonctionner dans ce profil. 3. Sous Linux, supprimer un profil de test : son `.db` et ses fichiers annexes doivent disparaître de `~/.config/com.simpl.resultat/`. 4. Sous Linux, les colonnes de rendement du Bilan doivent être remplies. 5. Un profil qui avait des classements manuels avant la mise à jour doit voir sa case 5 cochée. L'issue passe en `status:blocked` jusqu'à ces vérifications ; elle sera fermée à la main, avec les preuves. Suivis non bloquants de la revue : #395.
Author
Owner

Vérifications post-merge faites par Max sur la v0.17.1 publiée (tag sur e4db715, qui contient la PR #394) ; complétion confirmée le 2026-10-10. Les cinq points de la liste (commentaire 20191) sont faits :

  1. classer une transaction sur la page Transactions coche la case 5 du tableau de bord ;
  2. sous Linux, un vrai profil secondaire ancien rattrape ses migrations : ligne « database updated with versions … » au Journal, copie dans ~/.config/com.simpl.resultat/migration-backups/, import fonctionnel dans ce profil ;
  3. sous Linux, supprimer un profil de test retire son .db et ses fichiers annexes de ~/.config/com.simpl.resultat/ ;
  4. sous Linux, les colonnes de rendement du Bilan sont remplies ;
  5. un profil qui avait des classements manuels avant la mise à jour voit sa case 5 cochée.

Le détail des observations n'est pas consigné ici. Fermeture à la main, comme l'annonçait Refs. Les suivis non bloquants de la revue restent en #395.

Vérifications post-merge faites par Max sur la **v0.17.1 publiée** (tag sur `e4db715`, qui contient la PR #394) ; complétion confirmée le 2026-10-10. Les cinq points de la liste (commentaire 20191) sont faits : 1. classer une transaction sur la page Transactions coche la case 5 du tableau de bord ; 2. sous Linux, un vrai profil secondaire ancien rattrape ses migrations : ligne « database updated with versions … » au Journal, copie dans `~/.config/com.simpl.resultat/migration-backups/`, import fonctionnel dans ce profil ; 3. sous Linux, supprimer un profil de test retire son `.db` et ses fichiers annexes de `~/.config/com.simpl.resultat/` ; 4. sous Linux, les colonnes de rendement du Bilan sont remplies ; 5. un profil qui avait des classements manuels avant la mise à jour voit sa case 5 cochée. Le détail des observations n'est pas consigné ici. Fermeture à la main, comme l'annonçait `Refs`. Les suivis non bloquants de la revue restent en #395.
maximus removed the
status:blocked
label 2026-10-10 12:17:33 +00:00
Sign in to join this conversation.
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#390
No description provided.