fix: persister et restaurer le format d'import (bug racine) #324

Closed
opened 2026-08-12 20:14:32 +00:00 by maximus · 0 comments
Owner

Refs: spec-decisions-import-csv-format.md + spec-plan-import-csv-format.md (racine du repo).

Contexte

Le bug central du chantier. A la restauration d'une source configuree, src/hooks/useImportWizard.ts:311-325 :

amountMode: mapping.debitAmount !== undefined ? "debit_credit" : "single",
signConvention: "negative_expense",   // ecrit en dur

La convention est perdue a chaque rechargement et le mode est re-devine depuis la forme du mapping. useImportWizard.ts:514 applique alors la negation a contresens : toutes les depenses deviennent des revenus, sans erreur affichee. La doc Desjardins confirme que le cas est courant — le signe des montants differe selon le type de compte, et un meme utilisateur a un compte-cheques et une carte de credit dans deux conventions opposees.

Second defaut, symetrique : ColumnMappingEditor.tsx:82-94 ne nettoie pas le mapping au changement de mode (seuls les <select> le font). Basculer de mode sans re-selectionner de colonne perd ou inverse le mode au rechargement.

Troisieme defaut : la config est ecrite en base des l'etape doublons (useImportWizard.ts:588-606), donc un import annule persiste quand meme une configuration potentiellement fausse.

Taches

  • Type ImportFormat partage regroupant les huit champs de format ; ImportSource et ImportConfigTemplate le composent tous les deux
  • src/services/importSourceService.ts : les quatre nouvelles colonnes en creation, mise a jour et lecture
  • useImportWizard.selectSource : lire les vraies valeurs ; supprimer la valeur en dur (:323) et la re-inference du mode (:321)
  • ColumnMappingEditor.onAmountModeChange nettoie les colonnes du mode abandonne — le mode devient la source de verite du mapping
  • Deplacer l'ecriture de la config de checkDuplicatesInternal vers executeImport
  • Lier la source au modele applique (template_id), persiste et restaure ; selectedTemplateId n'est plus remis a null aveuglement (:300)
  • Test de cycle : configurer -> sauvegarder -> recharger -> le format est identique champ par champ

Criteres d'acceptation

  • Une source reglee en montants positifs conserve sa convention au deuxieme import et aux suivants
  • Basculer de mode puis recharger la source restitue le mode choisi
  • Un import abandonne ne laisse aucune configuration en base

Depends on #323


Revision /review-spec — 2026-08-13

Corrections a appliquer, issues de la revue 3 experts :

  • Le type ImportFormat compose est structurellement impossible — verifie dans src/shared/types/index.ts : ImportSource.has_header: boolean (:12) et column_mapping: string (:10), ImportConfigTemplate.has_header: number (:164), SourceConfig.hasHeader: boolean camelCase avec columnMapping: ColumnMapping objet (:225), et AutoDetectResult ne porte que 7 des 8 champs.
  • Remplacer par deux types et un codec :
    • ImportFormatRow — forme persistee, snake_case, mapping en JSON, has_header normalise.
    • ImportFormat — forme de domaine, camelCase, mapping parse.
    • formatToRow / formatFromRowseul point de conversion, avec un test de completude : un champ ajoute au format sans passer par le codec fait echouer le test.
  • La garantie contre une persistance partielle vient donc du codec et de son test, pas de la structure du type.

Fichiers concernes

  • src/shared/types/index.tsImportFormatRow + ImportFormat
  • src/utils/importFormat.tscreer : codec formatToRow / formatFromRow
  • src/services/importSourceService.ts — persistance des 4 colonnes
  • src/services/importConfigTemplateService.ts — alignement sur le codec
  • src/hooks/useImportWizard.ts — restauration reelle (:311-325), point d'ecriture deplace vers executeImport
  • src/components/import/ColumnMappingEditor.tsx — le mode nettoie le mapping oppose

Decisions prises en planification

  • Emplacement du code partage : le codec formatToRow/formatFromRow et mapRow vivent dans src/utils/importFormat.ts — meme dossier que amountParser, dateParser et csvAutoDetect, qui portent deja la logique pure du domaine. Les types restent dans src/shared/types/.
  • Aucune correction retroactive des transactions deja importees a l'envers : la voie de reparation est deleteImportWithTransactions puis re-import. Ne jamais muter des montants deja ecrits.
  • L'import reste entierement en edition Free — aucun gating a ajouter.
  • Les specs spec-decisions-import-csv-format.md et spec-plan-import-csv-format.md sont committees a la racine (force-add, precedent PR #295) : elles sont lisibles depuis un worktree.

Spec source

spec-plan-import-csv-format.md + spec-decisions-import-csv-format.md (racine du repo, committees).

Refs: `spec-decisions-import-csv-format.md` + `spec-plan-import-csv-format.md` (racine du repo). ## Contexte Le bug central du chantier. A la restauration d'une source configuree, `src/hooks/useImportWizard.ts:311-325` : ```ts amountMode: mapping.debitAmount !== undefined ? "debit_credit" : "single", signConvention: "negative_expense", // ecrit en dur ``` La convention est perdue a chaque rechargement et le mode est re-devine depuis la forme du mapping. `useImportWizard.ts:514` applique alors la negation a contresens : toutes les depenses deviennent des revenus, sans erreur affichee. La doc Desjardins confirme que le cas est courant — le signe des montants differe selon le type de compte, et un meme utilisateur a un compte-cheques et une carte de credit dans deux conventions opposees. Second defaut, symetrique : `ColumnMappingEditor.tsx:82-94` ne nettoie pas le mapping au changement de mode (seuls les `<select>` le font). Basculer de mode sans re-selectionner de colonne perd ou inverse le mode au rechargement. Troisieme defaut : la config est ecrite en base des l'etape doublons (`useImportWizard.ts:588-606`), donc un import annule persiste quand meme une configuration potentiellement fausse. ## Taches - [ ] Type `ImportFormat` partage regroupant les huit champs de format ; `ImportSource` et `ImportConfigTemplate` le composent tous les deux - [ ] `src/services/importSourceService.ts` : les quatre nouvelles colonnes en creation, mise a jour et lecture - [ ] `useImportWizard.selectSource` : lire les vraies valeurs ; supprimer la valeur en dur (`:323`) et la re-inference du mode (`:321`) - [ ] `ColumnMappingEditor.onAmountModeChange` nettoie les colonnes du mode abandonne — le mode devient la source de verite du mapping - [ ] Deplacer l'ecriture de la config de `checkDuplicatesInternal` vers `executeImport` - [ ] Lier la source au modele applique (`template_id`), persiste et restaure ; `selectedTemplateId` n'est plus remis a `null` aveuglement (`:300`) - [ ] Test de cycle : configurer -> sauvegarder -> recharger -> le format est identique champ par champ ## Criteres d'acceptation - [ ] Une source reglee en montants positifs conserve sa convention au deuxieme import et aux suivants - [ ] Basculer de mode puis recharger la source restitue le mode choisi - [ ] Un import abandonne ne laisse aucune configuration en base Depends on #323 --- ## Revision /review-spec — 2026-08-13 Corrections a appliquer, issues de la revue 3 experts : - **Le type `ImportFormat` compose est structurellement impossible** — verifie dans `src/shared/types/index.ts` : `ImportSource.has_header: boolean` (`:12`) et `column_mapping: string` (`:10`), `ImportConfigTemplate.has_header: number` (`:164`), `SourceConfig.hasHeader: boolean` camelCase avec `columnMapping: ColumnMapping` objet (`:225`), et `AutoDetectResult` ne porte que 7 des 8 champs. - **Remplacer par deux types et un codec** : - `ImportFormatRow` — forme persistee, snake_case, mapping en JSON, `has_header` normalise. - `ImportFormat` — forme de domaine, camelCase, mapping parse. - `formatToRow` / `formatFromRow` — **seul point de conversion**, avec un test de completude : un champ ajoute au format sans passer par le codec fait echouer le test. - La garantie contre une persistance partielle vient donc du codec et de son test, pas de la structure du type. --- ## Fichiers concernes - `src/shared/types/index.ts` — `ImportFormatRow` + `ImportFormat` - `src/utils/importFormat.ts` — **creer** : codec `formatToRow` / `formatFromRow` - `src/services/importSourceService.ts` — persistance des 4 colonnes - `src/services/importConfigTemplateService.ts` — alignement sur le codec - `src/hooks/useImportWizard.ts` — restauration reelle (`:311-325`), point d'ecriture deplace vers `executeImport` - `src/components/import/ColumnMappingEditor.tsx` — le mode nettoie le mapping oppose ## Decisions prises en planification - **Emplacement du code partage** : le codec `formatToRow`/`formatFromRow` et `mapRow` vivent dans `src/utils/importFormat.ts` — meme dossier que `amountParser`, `dateParser` et `csvAutoDetect`, qui portent deja la logique pure du domaine. Les types restent dans `src/shared/types/`. - **Aucune correction retroactive** des transactions deja importees a l'envers : la voie de reparation est `deleteImportWithTransactions` puis re-import. Ne jamais muter des montants deja ecrits. - **L'import reste entierement en edition Free** — aucun gating a ajouter. - Les specs `spec-decisions-import-csv-format.md` et `spec-plan-import-csv-format.md` sont **committees a la racine** (force-add, precedent PR #295) : elles sont lisibles depuis un worktree. ## Spec source `spec-plan-import-csv-format.md` + `spec-decisions-import-csv-format.md` (racine du repo, committees).
maximus added this to the planned-2026-08-12-import-csv-format milestone 2026-08-12 20:14:32 +00:00
maximus added the
status:ready
type:bug
source:human
labels 2026-08-12 20:14:32 +00:00
maximus added
status:in-progress
and removed
status:ready
labels 2026-08-13 16:59:24 +00:00
maximus added
status:approved
and removed
status:in-progress
labels 2026-08-14 15:32:05 +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#324
No description provided.