129 lines
5.7 KiB
JavaScript
129 lines
5.7 KiB
JavaScript
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.
|
|
* - type='versement_annuel' (défaut) : objectif global, investisseur_id requis, plateforme_id absent.
|
|
* - type='capital_investi' : objectif par plateforme, plateforme_id requis ; investisseur_id est
|
|
* dérivé de la plateforme (ignoré s'il est fourni) car une plateforme appartient à un seul
|
|
* investisseur — cf. section Paramétrage > Fixation des objectifs.
|
|
*/
|
|
function parseBody(body) {
|
|
const { investisseur_id, plateforme_id, type = TYPE_DEFAUT, annee, montant, notes } = body;
|
|
if (!type?.trim()) throw new HttpError(400, 'type est requis');
|
|
const trimmedType = type.trim();
|
|
if (trimmedType === 'capital_investi') {
|
|
if (!plateforme_id) throw new HttpError(400, 'plateforme_id est requis pour un objectif capital_investi');
|
|
} else if (!investisseur_id) {
|
|
throw new HttpError(400, 'investisseur_id 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: investisseur_id ? Number(investisseur_id) : null,
|
|
plateforme_id: plateforme_id ? Number(plateforme_id) : null,
|
|
type: trimmedType,
|
|
annee: Number(annee),
|
|
montant: Number(montant),
|
|
notes: notes?.trim() || null,
|
|
};
|
|
}
|
|
|
|
/* ── GET /api/objectifs?type=&annee=&plateforme_id= ──────────────────────
|
|
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, plateforme_id } = 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)); }
|
|
if (plateforme_id) { conds.push('o.plateforme_id = ?'); args.push(Number(plateforme_id)); }
|
|
|
|
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 manuel (select puis update/insert) ─────
|
|
Pas de ON CONFLICT : deux index uniques partiels différents selon le type
|
|
(cf. db/index.js), plus simple et plus sûr de faire l'upsert à la main que
|
|
de viser le bon index partiel en SQL. */
|
|
router.post('/', (req, res, next) => {
|
|
try {
|
|
const data = parseBody(req.body);
|
|
|
|
let investisseurId = data.investisseur_id;
|
|
if (data.type === 'capital_investi') {
|
|
const plat = db.prepare('SELECT id, investisseur_id FROM plateformes WHERE id = ? AND user_id = ?')
|
|
.get(data.plateforme_id, req.user.id);
|
|
if (!plat) throw new HttpError(404, 'Plateforme introuvable');
|
|
investisseurId = plat.investisseur_id;
|
|
} else {
|
|
const inv = db.prepare('SELECT id FROM investisseurs WHERE id = ? AND user_id = ?')
|
|
.get(investisseurId, req.user.id);
|
|
if (!inv) throw new HttpError(404, 'Investisseur introuvable');
|
|
}
|
|
|
|
const existing = data.plateforme_id
|
|
? db.prepare('SELECT id FROM objectifs WHERE plateforme_id = ? AND type = ? AND annee = ?')
|
|
.get(data.plateforme_id, data.type, data.annee)
|
|
: db.prepare('SELECT id FROM objectifs WHERE investisseur_id = ? AND type = ? AND annee = ? AND plateforme_id IS NULL')
|
|
.get(investisseurId, data.type, data.annee);
|
|
|
|
if (existing) {
|
|
db.prepare(`UPDATE objectifs SET montant = ?, notes = ?, updated_at = datetime('now') WHERE id = ?`)
|
|
.run(data.montant, data.notes, existing.id);
|
|
} else {
|
|
db.prepare(`
|
|
INSERT INTO objectifs (investisseur_id, plateforme_id, type, annee, montant, notes)
|
|
VALUES (?,?,?,?,?,?)
|
|
`).run(investisseurId, data.plateforme_id, data.type, data.annee, data.montant, data.notes);
|
|
}
|
|
|
|
const saved = data.plateforme_id
|
|
? db.prepare(`
|
|
SELECT o.*, i.nom AS investisseur_nom
|
|
FROM objectifs o JOIN investisseurs i ON i.id = o.investisseur_id
|
|
WHERE o.plateforme_id = ? AND o.type = ? AND o.annee = ?
|
|
`).get(data.plateforme_id, data.type, data.annee)
|
|
: 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 = ? AND o.plateforme_id IS NULL
|
|
`).get(investisseurId, 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;
|