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;