Correctif

This commit is contained in:
ocroguennec committed 2026-09-02 08:28:15 +02:00
1 parent 0334a63139
commit 981ddf5830
7 files changed
+115 -42

No files matched your search

+4 -1
View File
@@ -8,7 +8,10 @@ import { requireAdmin } from '../middleware/auth.js';
import sharp from 'sharp';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const iconsDir = path.resolve(__dirname, '../../../data/icons');
const dataDir = process.env.DATA_DIR
? path.resolve(process.env.DATA_DIR)
: path.resolve(__dirname, '../../../data'); // fallback dev : 3 niveaux au-dessus de routes/
const iconsDir = path.join(dataDir, 'icons');
const historyDir = path.resolve(iconsDir, 'history');
fs.mkdirSync(iconsDir, { recursive: true });
fs.mkdirSync(historyDir, { recursive: true });
+4 -1
View File
@@ -10,7 +10,10 @@ import fs from 'node:fs';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const logosDir = path.resolve(__dirname, '../../../data/logos');
const dataDir = process.env.DATA_DIR
? path.resolve(process.env.DATA_DIR)
: path.resolve(__dirname, '../../../data'); // fallback dev : 3 niveaux au-dessus de routes/
const logosDir = path.join(dataDir, 'logos');
fs.mkdirSync(logosDir, { recursive: true });
/** Sanitise un nom de plateforme pour en faire un nom de fichier safe */
+21 -12
View File
@@ -17,12 +17,13 @@ const round2 = v => Math.round((v ?? 0) * 100) / 100;
* n'est pas soldé. Seuls les champs `interets.*` peuvent être filtrés
* par année via `?annee=`. `gain_net_depuis_debut` est TOUJOURS un cumul
* total (indépendant de `?annee=`) : intérêts nets + cashback/bonus de
* parrainage ou de plateforme + corrections de solde − frais réglés hors
* remboursement (porte-monnaie ou compte courant du détenteur, cf.
* /frais-operations — le détail est dans `gain_net_depuis_debut.frais_hors_remboursement`),
* sur tout l'historique du portefeuille — reste valable même après un retrait
* complet des plateformes, contrairement à une approche basée sur le
* solde courant.
* parrainage ou de plateforme + corrections de solde − tous les frais réellement payés en
* dehors du passage brut → net d'un remboursement (à la souscription, porte-monnaie ou
* compte courant du détenteur — cf. /frais-operations ; seul le mode "remboursement" est
* exclu, déjà déduit des intérêts nets ci-dessus). Détail dans
* `gain_net_depuis_debut.frais_hors_remboursement`. Sur tout l'historique du portefeuille —
* reste valable même après un retrait complet des plateformes, contrairement à une approche
* basée sur le solde courant.
* tags: [Dashboard]
* security: [{ ApiKeyAuth: [] }]
* parameters:
@@ -123,16 +124,24 @@ router.get('/', (req, res) => {
WHERE ${invCond('investisseur_id')}
`).get(invParam);
// ── Frais hors remboursement (Étape 5, 01/09/26 — cf. project_gain_net_depuis_debut.md) :
// les frais réglés en mode "portefeuille" ou "compte_courant" sont une sortie d'argent réelle
// et séparée des remboursements (contrairement au mode "remboursement", déjà déduit de
// interets_nets ci-dessus) — ils doivent donc être soustraits du gain net, en miroir exact du
// correctif appliqué au KPI "Gain net depuis le début" de l'app interne (Dashboard.jsx).
// ── Frais hors remboursement (Étape 5, 02/09/26 — cf. project_gain_net_depuis_debut.md) :
// tout frais réel payé par l'investisseur SAUF ceux retenus directement sur un remboursement
// (mode "remboursement") est soustrait ici — ceux-là sont déjà déduits de interets_nets
// ci-dessus au moment du passage brut → net par la plateforme (ex. Fundora), les soustraire une
// 2ᵉ fois créerait une double déduction qui ne correspond à aucune sortie d'argent réelle
// supplémentaire. Les modes "source" (déduit du versement à la souscription — déjà netté dans
// montant_investi, une donnée de capital que ce total n'utilise jamais), "portefeuille" et
// "compte_courant" sont chacun une vraie sortie d'argent jamais reflétée ailleurs dans cette
// somme (qui ne comporte que des flux de revenu), donc tous les trois doivent être déduits — en
// miroir exact du correctif appliqué au KPI "Gain net depuis le début" de l'app interne
// (Dashboard.jsx). Ne pas réutiliser le filtre ('portefeuille','compte_courant') du XIRR net :
// celui-ci exclut "source" pour une raison propre au XIRR (déjà représenté par un flux de
// capital investi plus petit à t0), qui ne s'applique pas ici.
const fraisHorsRembRow = db.prepare(`
SELECT COALESCE(SUM(f.montant), 0) AS total
FROM frais_operations f
JOIN investissements i ON i.id = f.investissement_id
WHERE f.mode_reglement IN ('portefeuille','compte_courant') AND ${invCond('i.investisseur_id')}
WHERE f.mode_reglement != 'remboursement' AND ${invCond('i.investisseur_id')}
`).get(invParam);
const gainNetTotal = round2(
+3 -3
View File
@@ -18,9 +18,9 @@ const router = Router();
* détenteur (`compte_nom`), sans impact sur le porte-monnaie ; "remboursement" : retenu
* directement sur le remboursement lié (`remboursement_id`), déjà pris en compte dans les
* champs `interets_nets`/`net_recu` de ce remboursement — ne pas le compter une seconde
* fois. Seuls les modes "portefeuille" et "compte_courant" représentent une sortie
* d'argent réelle et séparée (c'est ce sous-ensemble qui est déduit de
* `gain_net_depuis_debut` sur /dashboard).
* fois. Tous les autres modes ("source", "portefeuille", "compte_courant")
* représentent chacun une sortie d'argent réelle (c'est cet ensemble — tout sauf
* "remboursement" — qui est déduit de `gain_net_depuis_debut` sur /dashboard).
* tags: [Frais]
* security: [{ ApiKeyAuth: [] }]
* parameters: