---
titre: Import de données — Reste à faire
statut: Planifiée
date_proposition: 2026-05-14
auteur: cguionet
priorite: P1
release_cible: v0.3 → v0.5
---

# 001 — Import de données : reste à faire

État au 2026-05-14 : le pipeline CSV synchrone est fonctionnel de bout en bout, validé sur les exports LCL Factory Patrimoine (MacRoman, 2-colonnes dates FR, mapping inline, anti-doublon par `occurrence_index`). Le détail des livrables du jour est dans [§ Journal d’implémentation — 2026-05-14](#journal-dimplémentation--2026-05-14). Ce qui suit liste ce qu'il reste à construire pour atteindre la cible « MVP import complet ».

## Journal d'implémentation — 2026-05-14

Livrés ou renforcés dans le code et la doc ce jour-là :

- **Schéma / entité** : colonne `transaction.occurrence_index` (défaut 1) + recalcul du `hash` SHA-256 incluant l’index, pour distinguer plusieurs opérations identiques (même jour, sens, montant, libellé normalisé) sur un même compte — migration [`Version20260514135431.php`](../../migrations/Version20260514135431.php), alignement [`Transaction`](../../src/Entity/Transaction.php).
- **Lien import → transaction** : `Transaction.importJob` (FK nullable, `SET NULL` en cascade) permet de rattacher une ligne à son `ImportJob` (prérequis pour un futur rollback ciblé).
- **Contrôleur** : garde sur l’étape mapping — uniquement pour les jobs `Csv` ; les PDF sont renvoyés vers la prévisualisation PDF. Upload étendu à `.csv`, `.txt`, `.pdf` avec branchement PDF vs CSV.
- **Runner / parser** : maintien du pipeline décrit dans la spec (modes date `single` / `two_columns_fr`, montants `signed` / `split`, mois FR dans `CsvImportRunner`, etc.).
- **Formulaire & UI** : `CsvMappingType` + template mapping avec table d’aperçu et selects par colonne ; [`public/js/import-mapping.js`](../../public/js/import-mapping.js) (modes date/montant, options mutuellement exclusives, récap rôles requis, validation UX avant submit).
- **Documentation** : spec [04-import-donnees.md](../specifications/04-import-donnees.md) actualisée (workflow, hash avec `occurrence_index`, mapping inline).

## Contexte

Le chantier import a démarré sur la spec [04-import-donnees.md](../specifications/04-import-donnees.md). La spec a été mise à jour pour refléter l'implémentation actuelle. Les éléments ci-dessous sont **explicitement non livrés** à ce stade.

## Reste à faire — par priorité

### P0 — Doit être fait avant beaucoup d'imports en parallèle

#### 1. Asynchrone via Symfony Messenger
- **Constat** : `CsvImportRunner::run()` est appelé directement dans `ImportController::mapping()`. La requête HTTP reste ouverte tant que les 102 lignes ne sont pas traitées (~150 ms sur le fichier test, mais x100 sur un export annuel ou un PDF).
- **Faire** :
  - Créer `App\Message\RunImportJob` (juste un `int $jobId`).
  - `MessageHandler` qui appelle `CsvImportRunner::run(ImportJob)`.
  - Le controller dispatch le message au lieu d'appeler le runner directement.
  - Configurer un transport `async` (Doctrine ou Redis selon prod cible).
  - Worker en service Compose (`viizia-worker`).
- **Effort** : Petit (1-2 h, archi déjà classique Symfony).

#### 2. Polling de progression côté UI
- **Constat** : pendant l'exécution sync, l'utilisateur attend sur une page blanche (Symfony render bloqué). Avec l'async ce sera pire (redirection immédiate vers une page « Running »).
- **Faire** :
  - Endpoint `GET /admin/imports/{id}/status` renvoyant JSON `{status, total, imported, skipped, percent}`.
  - Page show qui polle toutes les 2 s tant que `status in [Pending, Running]`.
  - Barre de progression Bulma.
- **Effort** : Petit (1 h).

### P1 — Quality of life

#### 3. Profils de mapping par compte
- **Constat** : à chaque import sur le même compte, l'utilisateur re-passe par l'auto-détection des en-têtes. Si l'export bancaire est stable (toujours les mêmes colonnes), c'est du clic redondant.
- **Faire** :
  - Colonne `bank_account.csv_mapping` (JSON, nullable). Migration.
  - À la validation du mapping qui réussit (≥1 transaction importée), persister le mapping dans `bank_account.csv_mapping`.
  - Au prochain upload sur ce compte : préférer ce mapping persisté à l'auto-détection.
  - Bouton « Réinitialiser le mapping de ce compte » sur la page édition de compte.
- **Effort** : Moyen (3 h — migration + UI bank_account + injection dans le controller).

#### 4. Profils prédéfinis par banque (seed)
- **Constat** : un nouvel utilisateur LCL / BNP / Société Générale doit refaire l'auto-détection alors que les formats sont publics et connus.
- **Faire** :
  - Entité (ou config YAML) `BankProfile` avec nom + regex de détection des en-têtes + mapping de référence.
  - DataFixtures pour BNP Paribas, SG, Crédit Agricole, LCL, BPCE, Boursorama, Revolut, N26, Qonto.
  - Au preview : tenter de matcher le profil sur les en-têtes (ex. présence de "Compte LCL" en col 3 → profil LCL).
  - Si match : pré-remplir le mapping avec le profil au lieu de l'auto-détection regex.
- **Effort** : Moyen (4-5 h — la collecte des formats prend plus de temps que le code).

#### 5. Rollback d'un import
- **Constat** : pas de bouton « Annuler cet import » sur la page rapport. Si l'utilisateur s'est planté de compte, il doit supprimer les transactions à la main.
- **Constat technique (2026-05-14)** : la FK `transaction.import_job_id` est en place sur l'entité — chaque transaction créée par un import peut référencer son job. **Il manque encore** la fonctionnalité produit (bouton, soft-delete groupé, solde, statut `RolledBack`, fenêtre 24 h).
- **Faire** :
  - Bouton « Annuler et supprimer les transactions importées » sur la page rapport.
  - Soft-delete (`deleted_at`) sur les transactions dont `import_job_id = X`.
  - Mettre à jour le solde du compte.
  - **Condition** : disponible 24 h après l'import (champ `import_job.rollback_deadline` ou simple comparaison `created_at > now-24h`).
  - Statut du job → `RolledBack` (nouvelle valeur enum).
- **Effort** : Moyen (~3 h côté métier/UI une fois la règle métier figée).

#### 6. Sauvegarde du mapping après validation, même partielle
Actuellement le mapping est sauvegardé même si l'import échoue à 100 %. C'est intentionnel pour le replay, mais ça pollue la table. À évaluer : nettoyer les `ImportJob` Failed > 7 j ou les marquer pour suppression.

#### 12. Réapplication des mémos « mots-clés » après import — livré
- **Imports PDF (`pdf-review`)** : **Détecter les mots-clés** (`POST …/pdf-review/refresh-keyword-suggestions`) pour statut **Partiel** ou **Succès** tant que le mapping `pdf_review` est présent ; recalcule les suggestions sur **toutes** les lignes du tableau (pending, importées, ignorées) sans toucher aux transactions (`PdfImportReviewService::refreshPdfReviewRowKeywordSuggestions`).
- **Imports PDF (`pdf-review`)** : **Réappliquer les règles mémo** sur les transactions liées au job (`POST …/reapply-enrichment-rules`), statuts **Partiel** ou **Succès** ; même page pour flux PDF après « Succès » (réouverture historique depuis le rapport).
- **Liste des transactions (`/admin/transactions`)** : **Appliquer les mots-clés d’import (mémo)** sur les transactions **de la page courante** (`POST …/transactions/apply-memo-suggestions`, `ImportTransactionEnrichmentReapplyService::reapplyForOrganizationTransactionIds`).
- Les **imports CSV** : même `POST …/reapply-enrichment-rules`, statuts **Partiel** ou **Succès** ; pas de formulaire dédié sur le rapport.
- Matching mémo : comparé au libellé normalisé (`LabelCaseFormatter` puis `Transaction::normalizeLabel`) ; **sous-chaîne** pour les règles dont la clé fait au moins **2** caractères (`ImportEnrichmentMemoService::SUBSTRING_MATCH_MIN_CHARS`, aligné sur la saisie des mots-clés catégorie). Les très courts motifs peuvent théoriquement matcher par hasard dans un libellé long ; privilégier des termes discriminants si besoin.
- **Sens debit/crédit** : une **recette** n’est suggérée / appliquée que sur une ligne **entrée** ; une **dépense**, sur une **sortie** ; les **virements internes** peuvent être des deux sens. Si l’extrait PDF ou le CSV classe à tort un virement Booking en sortie alors que ce sont vos encaissements, le mot-clé `booking.com` matche bien en base mais la catégorie recette est **volontairement ignorée** (`ImportEnrichmentMemoService::categoryFitsDirection`) — la revue PDF affiche dans ce cas un message **Mot-clé bloqué (sens)** / aide sous la liste déroulante.
- **Relevé LCL layout** : le libellé affiché en tableau peut être tronqué à 500 car. alors que le détail (« Vir Sepa Booking.com… ») vit dans une suite de lignes ; le matching mémo utilise désormais `listingMatchLabel` (chaîne élargie) comme pour les listings, pour ne pas perdre les mots-clés présents seulement dans la suite (`PdfImportReviewService::rowLabelForMemoMatch`).

### P2 — Nice to have

#### 7. Validation MIME et antivirus
- **Constat** : on valide juste l'extension (`.csv`/`.txt`). Un utilisateur malveillant pourrait uploader un binaire renommé.
- **Faire** :
  - Validation MIME via `Symfony\Component\HttpFoundation\File\File::getMimeType()` (autoriser `text/csv`, `text/plain`, `application/csv`).
  - Antivirus ClamAV en service Compose séparé (`viizia-clamav`), scan en pre-import.
- **Effort** : Petit pour MIME, Moyen pour ClamAV (1 h + 3 h).

#### 8. Auto-cleanup des fichiers > 90 jours
- **Constat** : les CSV uploadés s'empilent dans `/uploads/` indéfiniment. Sur 5 ans avec 12 imports/an/org × 1000 orgs ≈ 60 000 fichiers.
- **Faire** :
  - Commande `bin/console app:imports:cleanup` qui scanne `/uploads/` et supprime les fichiers > 90 j dont l'`import_job` est `Succeeded` ou `Failed`.
  - Cron quotidien.
  - Champ `import_job.retain_until` pour exceptions explicites.
- **Effort** : Petit (1-2 h).

#### 9. Format PDF
- **Constat** : la spec mentionne le PDF mais rien n'est implémenté.
- **Faire** : voir spec section « Format PDF — non implémenté ». Parsers dédiés par banque + fallback validation manuelle. **Gros chantier** — probablement à découper en sous-RFC.
- **Effort** : XL (2-3 semaines selon le nombre de banques cibles).

### P3 — Long terme

#### 10. Entité `Statement` (relevé)
- **Constat** : la spec initiale parlait d'un objet `Statement` lié à `ImportJob`, mais on n'en a pas créé. Pour l'instant `ImportJob` porte tout. Quand on aura les PDFs avec solde d'ouverture / fermeture / IBAN détectés, il faudra modéliser un vrai relevé.
- **Effort** : Moyen (4 h — entité + migration + refacto contrôleur).

#### 11. Statuts d'import plus fins
- Aujourd'hui : `Pending`, `Running`, `Succeeded`, `Partial`, `Failed`.
- À envisager : `RolledBack` (cf. RFC §5), `Reviewing` (étape de validation manuelle pour PDF non-reconnu).

## Décision

- **P0** (Messenger + polling) : à faire avant d'ouvrir l'import à plus d'un utilisateur en prod.
- **P1** (profils compte + presets banques + rollback) : à inclure dans la v0.3.
- **P2** (sécurité fichiers + cleanup) : v0.4.
- **P3** (PDF, Statement) : roadmap explicite, RFC dédiée à découper.

Dates fermes à fixer après planning de release.
