Nouvelles features: revision des conditions des prêts

This commit is contained in:
2026-07-12 19:14:27 +02:00
parent ef4576d160
commit 96effbe15a
5 changed files with 552 additions and 7 deletions
+33 -1
View File
@@ -1,5 +1,5 @@
# MEMORY.md — Crowdlending Tracker
*Dernière mise à jour: 2026-07-03 (session 8)*
*Dernière mise à jour: 2026-07-12 (session 10)*
---
@@ -508,3 +508,35 @@ const isBonus = BONUS_VALUES.includes(form.investissement_id);
### Piège outillage — mount bash périmé après édition Edit/Write
- Après une édition via l'outil `Edit`/`Write` (côté fichier réel), une vérification immédiate via `mcp__workspace__bash` (wc/tail/node --check) peut montrer une **version périmée/tronquée** du fichier alors que le fichier réel est complet et correct — le mount bash ne se resynchronise pas instantanément après une écriture Windows-side.
- **Règle** : vérifier l'intégrité d'un fichier édité via l'outil `Read` (relire la queue, vérifier la fermeture propre), pas via bash. N'utiliser bash pour vérifier que si l'écriture a elle-même été faite depuis bash (ex. reconstruction `python3` après troncature confirmée par `Read`).
---
## Session 10 — Bug racine dates prêts différés + audit trail + statuts auto (2026-07-12)
### 🔴 Bug racine trouvé et corrigé — migration `db/index.js` recalculait `date_cible` à CHAQUE démarrage
- **Symptôme initial** : des dizaines de prêts `differe` avec `date_cible` aberrante (années 2100 à 2650), sans aucune trace dans `investissement_historique`, y compris des cas de "ping-pong" de statut (`en_cours``en_retard`) sur les mêmes prêts toutes les quelques minutes.
- **Cause réelle** (migration "renommage `date_debut``date_premiere_echeance`, `date_echeance``date_cible`", `backend/src/db/index.js` ~ligne 229) : le bloc de "correction de formule historique" (`SET date_cible = date(date_premiere_echeance, '+(duree_mois-1)' months)`) n'avait **aucune garde** (`WHERE date_cible IS NULL`) — il tournait donc à **chaque redémarrage serveur**, pour **tous** les investissements. Combiné à une 2e migration juste après (`date_premiere_echeance = date_cible` pour les prêts différés, censée maintenir l'égalité des deux dates), cela formait une **boucle infinie** : à chaque redémarrage, `date_cible` dérivait de +(duree_mois-1) mois supplémentaires — jamais tracé, car du code de migration, pas une action utilisateur ni une route API.
- **Pourquoi ça n'a été détecté que maintenant** : le serveur redémarre très souvent en dev (`node --watch`), donc chaque édition de code déclenchait un cycle de dérive supplémentaire sur les prêts différés déjà touchés.
- **Fix** : le bloc de correction ne s'exécute désormais que si `migrationEnCours` est vrai (colonnes `date_debut`/`date_echeance` encore présentes, càd le jour réel du renommage) — plus jamais au démarrage normal. La migration `date_premiere_echeance = date_cible` (prêts différés) enregistre maintenant un historique précis (`correction_auto_echeancier`) quand elle modifie quelque chose.
- **Point d'attention pour le futur** : toute migration de données (pas juste `ALTER TABLE ADD COLUMN`) dans `db/index.js` doit être strictement idempotente/one-shot — soit via clause `WHERE champ IS NULL`, soit gardée derrière la détection de l'événement historique qui la justifie. Ne jamais laisser un `UPDATE` sans garde tourner à chaque boot.
### Autres endroits déjà audités/corrigés pour la même classe de bug (dates modifiées sans trace)
- `POST /api/imports/dossier` (`imports.js`) : la branche "SCÉNARIO UPDATE" (upsert par nom_projet+date_souscription) écrasait `date_cible`/`date_premiere_echeance`/`montant_investi`/`taux_interet`/`duree_mois`/`type_remb`/`statut` avec seulement un historique générique ("Mise à jour dossier"), sans le détail des champs modifiés. Fix : réutilise `detectChangements`/`recordHistory`/`detectTypeEvenement` (désormais **exportés** depuis `investissements.js`) pour logger un diff précis champ par champ, comme l'édition manuelle.
- `backend/fix_dates_cible.mjs` : script autonome (`node fix_dates_cible.mjs`), jamais appelé automatiquement, corrige les `date_cible > 2100-01-01` — toujours présent mais pas la source du bug de cette session.
### Nouveautés Nettoyage de données (`Settings.jsx` → section `imports`, composant `DataCleanupSection.jsx`)
1. **"Corriger les dates des prêts différés"** (`POST /investissements/fix-differe-dates`) :
- Seuil d'écart désormais **configurable** via un select dans la modale : 3 / 6 / 12 / 18 / 24 mois (défaut 24, avant en dur "2 ans"). Body `{ seuilMois }`, validé côté backend contre `[3,6,12,18,24]`.
- Régénère maintenant `simul_remboursements` via `generateSimul()` après correction (avant : échéancier laissé désynchronisé).
- Déclenche immédiatement `checkStatutsRetard()` après correction (sinon un prêt dont la date recalculée tombe dans le passé restait affiché "en_cours" jusqu'au prochain minuit).
2. **"Vérifier la cohérence de l'échéancier des prêts différés"** (nouveau, `POST /investissements/check-echeancier-differe`) — inséré juste après le précédent. Vérifie que `simul_remboursements` contient exactement 1 échéance à la date `date_premiere_echeance`/`date_cible` du prêt ; régénère sinon (`generateSimulWithReinvestissements` si réinvestissements présents, sinon `generateSimul`), log `correction_auto_echeancier`.
### Job `autoStatut.js` — statuts automatiques enrichis
- `checkStatutsRetard()` fait maintenant **les deux sens** : `en_cours→en_retard` (comme avant, log `passage_auto_retard`) ET `en_retard→en_cours` (nouveau, log `retour_auto_en_cours`) — mais uniquement si (a) `date_cible` est entre aujourd'hui et +30 ans (garde-fou anti date-encore-aberrante-mais-"future") ET (b) le dernier événement d'historique touchant le statut était bien `passage_auto_retard` (jamais d'annulation automatique d'un passage en retard décidé manuellement).
- Chaque transition génère une **notification utilisateur** (table `notifications`) : type `warning` pour le passage en retard, type `success` pour le retour en cours, avec lien direct `/investissements/:id`.
- `TRACKED_FIELDS`, `recordHistory`, `detectChangements`, `detectTypeEvenement` sont maintenant `export` depuis `investissements.js` (réutilisés par `imports.js`).
### Piège outillage — lecture de la DB SQLite en direct depuis le sandbox bash (WAL + serveur actif)
- Le fichier réel `backend/data/crowdlending.db` est en mode WAL et activement écrit par le serveur Node de l'utilisateur pendant la session. Une copie manuelle (`cp` séparé du `.db`/`.db-wal`/`.db-shm`) pendant que le serveur écrit produit des **lectures tronquées ("database disk image is malformed") ou incohérentes entre deux requêtes successives** — ce n'est PAS la preuve que les données changent réellement à chaque lecture.
- `better-sqlite3` du repo est un binaire natif **Windows** (`node_modules/better-sqlite3/build/Release/better_sqlite3.node`) → `invalid ELF header` dans le sandbox Linux. Utiliser `python3` + module `sqlite3` standard à la place, sur une copie locale dans `/tmp`.
- Pour une lecture fiable d'un état ponctuel : privilégier les preuves de haut niveau déjà présentes dans l'app (page Historique du prêt, page Notifications avec horodatage) plutôt que des requêtes SQL répétées sur une DB en cours d'écriture concurrente.