Mise en place des objectifs

This commit is contained in:
2026-07-13 17:53:22 +02:00
parent 63f38b5ff0
commit 86a047a0c5
7 changed files with 508 additions and 1 deletions
+62 -1
View File
@@ -1,5 +1,5 @@
# MEMORY.md — Crowdlending Tracker
*Dernière mise à jour: 2026-07-13 (session 11)*
*Dernière mise à jour: 2026-07-13 (session 13)*
---
@@ -151,6 +151,7 @@ GET /api/fiscal-2778/export?annee=YYYY
GET/POST /api/pfu
GET/POST/PUT/DELETE /api/notation
GET/POST/PUT/DELETE /api/garanties
GET/POST/DELETE /api/objectifs?type=&annee=
GET/POST/PUT/DELETE /api/categories
POST /api/imports/preview | apply
GET /api/imports/history
@@ -572,3 +573,63 @@ Distincte de `investissement_historique` (audit générique auto-détecté sur t
### Piège outillage — confirmé à nouveau (cf. session 9)
Le mount bash de `crowdlending-app` était figé sur un instantané ancien (dates de fichiers plusieurs semaines avant la session), sans lien avec les éditions faites via l'outil `Edit`/`Write` dans cette session — `wc -l`/`node --check` sur le mount bash donnaient un fichier tronqué non représentatif. Solution utilisée : reconstruire le fichier édité dans le dossier `outputs` (accessible en écriture réelle depuis bash) via lecture complète par l'outil `Read`, puis `node --check` / `esbuild --loader=jsx` dessus pour une vérification syntaxique fiable — en complément (pas remplacement) de la relecture visuelle des zones éditées via `Read`.
---
## Session 12 — KPI XIRR sur la page Plateformes (2026-07-13)
### Rendement annualisé (XIRR) estimé pour les prêts en cours + renommage KPI
Sur `InvestissementDetail.jsx`, le KPI "Rendement annualisé" a été renommé **"XIRR — Brut/Net"** et calculé désormais pour **tous les statuts** (plus seulement `rembourse`) : pour un prêt non soldé, on ajoute un flux de trésorerie synthétique final = capital restant dû, daté du jour ("valorisation à date"), en plus des flux réels (versement initial, réinvestissements, remboursements). Le label affiche "(estimé)" tant que le prêt n'est pas intégralement remboursé. Un popup (icône info + `cell-tooltip tooltip-down`) explique la méthodologie. Le modificateur CSS `.tooltip-down` (`top: calc(100% + 6px)`) a été ajouté car le tooltip par défaut s'ouvre vers le haut et était coupé en haut de viewport pour ce KPI proche du sommet de page.
### Fonction `xirr()` extraite en utilitaire partagé
Déplacée de `InvestissementDetail.jsx` (définition locale) vers `frontend/src/utils/xirr.js` (export nommé `xirr`), pour réutilisation sans duplication. Newton-Raphson sur flux datés, retourne `null` si non convergent ou < 2 flux.
### Nouveau KPI XIRR sur la page Plateformes
Ajouté en 6e position dans `dr-kpi-row` de `Plateformes.jsx` (grid passée de `repeat(5,1fr)` à `repeat(6,1fr)`), juste après "Intérêts perçus" — même format que la fiche investissement (label + icône info + tooltip explicatif, "(estimé)" si applicable).
- Calcul agrégé (`plateformeXirr`, `useMemo`) sur `chartRows` (déjà filtré plateforme + détenteur + année) : flux datés individuels (pas des sommes) — investissement initial (négatif) + réinvestissements (négatif) + remboursements réels (positif, brut = capital+cashback+interets_bruts, net = `net_recu`), tous filtrés par la même coupure `cutoff` que les autres agrégats de la page (`${selectedYear}-12-31` si une année est sélectionnée, sinon `today()`).
- Valorisation à date : pour chaque investissement de `chartRows` encore actif (capital restant dû > 0 à la coupure), un flux positif = capital restant est ajouté à la date de coupure — cohérent avec la logique déjà utilisée pour `totals.encours`.
- Flux triés chronologiquement avant l'appel à `xirr()` (invariant mathématiquement par rapport à l'ancre `t0`, mais plus robuste/lisible).
- Choix assumé : pas de `TrendBadge` (comparaison N-1) sur ce KPI — contrairement aux 5 autres — car un XIRR n'est pas additif d'une année sur l'autre comme un total cumulé ; à la place, un sous-texte indique la date de valorisation ("Valorisé au 31/12/2025" ou "Valorisé au aujourd'hui").
### Piège outillage — reconfirmé une 3e fois (sessions 9, 11, 12)
Le mount bash reste figé sur un instantané qui ne reflète pas les éditions de la session (`wc -l` sur le mount stagnait à 1591 lignes alors que le fichier réel via l'outil `Read` en comptait 1675, et ce même après un nouveau `cp` explicite). Vérification faite intégralement via relecture complète du fichier par l'outil `Read` (comptage de lignes, relecture des zones éditées, vérification de fermeture des blocs). Ne plus perdre de temps à essayer de resynchroniser le mount bash pour de la vérification syntaxique sur ce projet — se fier à une relecture `Read` complète et méthodique en priorité.
---
## Session 13 — Objectifs annuels de versement (2026-07-13)
### Nouvelle fonctionnalité : suivi d'objectifs de versement annuel
Demande initiale : pouvoir fixer un objectif de dépôts nets par an et suivre l'écart, en partant de la page Plateformes (onglet Dépôts/Retraits), avec une architecture réutilisable ailleurs.
**Décisions produit actées après clarification (questions posées avant implémentation, conformément à la règle "poser des questions avant tâche complexe")** :
- Objectif défini **par investisseur et par année** (pas global, pas par plateforme) — en vue "tous les investisseurs", affichage = somme des objectifs individuels.
- Le suivi est **toujours calculé sur le portefeuille entier** (toutes plateformes), indépendamment du filtre plateforme actif sur la page qui l'affiche.
### Modèle de données
Nouvelle table `objectifs` (migration dans `db/index.js`, avant `export default db`) :
```sql
CREATE TABLE objectifs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
investisseur_id INTEGER NOT NULL REFERENCES investisseurs(id) ON DELETE CASCADE,
type TEXT NOT NULL DEFAULT 'versement_annuel',
annee INTEGER NOT NULL,
montant REAL NOT NULL,
notes TEXT,
created_at/updated_at TEXT,
UNIQUE(investisseur_id, type, annee)
)
```
Le champ `type` est prévu pour réutiliser la table plus tard (ex. `'rendement_annuel'`) sans nouvelle migration — seul `'versement_annuel'` est utilisé actuellement.
### Backend
`backend/src/routes/objectifs.js` (monté sur `/api/objectifs` dans `server.js`, entre `garantiesRouter` et `reinvestissementsRouter`) : `GET /` (filtres `?type=&annee=`, scope implicite = tous les investisseurs de `req.user.id` via jointure), `POST /` (upsert `ON CONFLICT(investisseur_id, type, annee)`), `DELETE /:id`. Pattern calqué sur `notation.js` (pas de middleware `requireInvestisseur`, ownership vérifiée par jointure SQL).
### Composant frontend réutilisable
`frontend/src/components/SuiviObjectifs.jsx` — props : `rows` (mouvements bruts dépôts/retraits), `investisseurs`, `scopeInvestisseurIds` (tous les ids en vue "all", sinon `[activeId]`), `objectifType` (défaut `'versement_annuel'`), `title`. Gère lui-même le fetch/upsert/delete des objectifs. Tableau année par année : Année | Dépôts | Retraits | Différence | **Objectif annuel** | Écart, + **ligne Total** en `<tfoot>` (somme des colonnes sur les années affichées ; Objectif/Écart totaux affichent "—" si aucune année n'a d'objectif défini). Édition : clic sur la cellule Objectif annuel → un input par investisseur du scope (un seul si scope=1), Enregistrer fait un `Promise.all` de POST upsert (et DELETE si champ vidé). Bouton "+ Ajouter une année" pour anticiper une année future sans mouvement.
### Intégration (2 emplacements)
1. `Plateformes.jsx` — onglet Dépôts/Retraits, section ajoutée sous le tableau "Mouvements de trésorerie" (`rows={allDepots}`).
2. `DepotsRetraits.jsx` (page dédiée du menu latéral) — **onglet dédié "Vision annuelle"** dans `.dr-tabs`, positionné entre "Plateformes" et "Vision mensuelle" (`activeTab === 'vision-annuelle'`, `rows={allRows}`). Le KPI existant "Diff. Dépôts vs Retraits" est enrichi d'une ligne sous le chiffre : "Reste X € pour l'objectif AAAA" ou "+X € au-delà de l'objectif AAAA" (calcul indépendant du filtre plateforme, année = `drPlatYear` ou année en cours ; nécessite un fetch local `objectifsKpi` séparé de celui du composant, car le composant gère son propre state).
### Piège outillage — vérification syntaxique de fichiers JSX volumineux
Le mount bash reste périmé (cf. sessions précédentes). Pour vérifier un **nouveau** composant JSX isolé (pas une édition dans un fichier existant de 1000+ lignes), la méthode fiable trouvée cette session : `npm init -y && npm install esbuild` dans `/tmp` (indépendant du node_modules Windows du projet, incompatible avec le sandbox Linux), copier le contenu exact du fichier via `cat > fichier << 'EOF'` puis `esbuild fichier.jsx --bundle --format=esm --jsx=automatic --external:react --outfile=...`. Pour une **édition ponctuelle** dans un gros fichier existant, la relecture `Read` ciblée des zones modifiées (comptage d'accolades/balises JSX) reste suffisante et plus rapide.