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
+88
View File
@@ -0,0 +1,88 @@
import { Router } from 'express';
import db from '../db/index.js';
import { HttpError } from '../middleware/errorHandler.js';
const router = Router();
const TYPE_DEFAUT = 'versement_annuel';
/** Valide et normalise le corps d'une requête POST */
function parseBody(body) {
const { investisseur_id, type = TYPE_DEFAUT, annee, montant, notes } = body;
if (!investisseur_id) throw new HttpError(400, 'investisseur_id est requis');
if (!type?.trim()) throw new HttpError(400, 'type est requis');
if (!Number.isInteger(Number(annee)) || Number(annee) < 2000 || Number(annee) > 2100) {
throw new HttpError(400, 'annee invalide');
}
if (montant === undefined || montant === null || Number.isNaN(Number(montant)) || Number(montant) < 0) {
throw new HttpError(400, 'montant doit être un nombre positif');
}
return {
investisseur_id: Number(investisseur_id),
type: type.trim(),
annee: Number(annee),
montant: Number(montant),
notes: notes?.trim() || null,
};
}
/* ── GET /api/objectifs?type=&annee= ─────────────────────────────────────
Retourne tous les objectifs des investisseurs de l'utilisateur connecté
(toutes plateformes / tout le portefeuille — pas de notion de scope=all
vs single ici, un objectif appartient toujours à un investisseur donné). */
router.get('/', (req, res) => {
const { type, annee } = req.query;
const conds = ['i.user_id = ?'];
const args = [req.user.id];
if (type) { conds.push('o.type = ?'); args.push(type); }
if (annee) { conds.push('o.annee = ?'); args.push(Number(annee)); }
const rows = db.prepare(`
SELECT o.*, i.nom AS investisseur_nom
FROM objectifs o
JOIN investisseurs i ON i.id = o.investisseur_id
WHERE ${conds.join(' AND ')}
ORDER BY o.annee DESC, i.nom
`).all(...args);
res.json(rows);
});
/* ── POST /api/objectifs ── upsert (investisseur_id, type, annee) ───────── */
router.post('/', (req, res, next) => {
try {
const data = parseBody(req.body);
const inv = db.prepare('SELECT id FROM investisseurs WHERE id = ? AND user_id = ?')
.get(data.investisseur_id, req.user.id);
if (!inv) throw new HttpError(404, 'Investisseur introuvable');
db.prepare(`
INSERT INTO objectifs (investisseur_id, type, annee, montant, notes)
VALUES (?,?,?,?,?)
ON CONFLICT(investisseur_id, type, annee)
DO UPDATE SET montant = excluded.montant, notes = excluded.notes, updated_at = datetime('now')
`).run(data.investisseur_id, data.type, data.annee, data.montant, data.notes);
const saved = db.prepare(`
SELECT o.*, i.nom AS investisseur_nom
FROM objectifs o JOIN investisseurs i ON i.id = o.investisseur_id
WHERE o.investisseur_id = ? AND o.type = ? AND o.annee = ?
`).get(data.investisseur_id, data.type, data.annee);
res.status(201).json(saved);
} catch (e) { next(e); }
});
/* ── DELETE /api/objectifs/:id ────────────────────────────────────────── */
router.delete('/:id', (req, res, next) => {
try {
const existing = db.prepare(`
SELECT o.id FROM objectifs o
JOIN investisseurs i ON i.id = o.investisseur_id
WHERE o.id = ? AND i.user_id = ?
`).get(req.params.id, req.user.id);
if (!existing) throw new HttpError(404, 'Objectif introuvable');
db.prepare('DELETE FROM objectifs WHERE id = ?').run(req.params.id);
res.json({ deleted: true });
} catch (e) { next(e); }
});
export default router;