This commit is contained in:
ocroguennec committed 2026-09-18 17:09:31 +02:00
1 parent 27c4c57a58
commit 6bba34396c
3 files changed
+313 -41

No files matched your search

+102
View File
@@ -1,9 +1,13 @@
import { Router } from 'express';
import { z } from 'zod';
import path from 'node:path';
import fs from 'node:fs';
import db from '../db/index.js';
import { HttpError } from '../middleware/errorHandler.js';
import { requireInvestisseur } from '../middleware/investisseurScope.js';
import { resolveActiveWorkspaceId } from '../middleware/workspaceScope.js';
import { createZip, sanitizeZipPart } from '../utils/zip.js';
import { docsDir } from './documents.js';
const router = Router();
@@ -102,6 +106,7 @@ router.get('/', (req, res) => {
const rows = db.prepare(`
SELECT ipe.*,
p.nom AS plateforme_nom,
p.logo_filename AS plateforme_logo,
inv.nom AS investisseur_nom,
(SELECT v.valorisation_nette FROM investissements_pe_valorisations v
WHERE v.investissement_pe_id = ipe.id
@@ -211,6 +216,7 @@ router.get('/:id', (req, res, next) => {
const row = db.prepare(`
SELECT ipe.*,
p.nom AS plateforme_nom,
p.logo_filename AS plateforme_logo,
inv.nom AS investisseur_nom,
(SELECT v.valorisation_nette FROM investissements_pe_valorisations v
WHERE v.investissement_pe_id = ipe.id
@@ -240,6 +246,102 @@ router.get('/:id', (req, res, next) => {
} catch (e) { next(e); }
});
/**
* GET /api/investissements-pe/:id/export — dossier ZIP d'un deal PE (18/09/26, demande
* Olivier — menu "⋮" de la fiche détail, inspiré du même menu côté crowdlending). Même
* principe que GET /investissements/:id/export (manifest.json + documents/), mais le
* contenu du manifeste reflète le modèle de données PE, plus réduit : pas d'échéancier
* prévisionnel, pas d'historique de modifications, pas de révisions de conditions, pas de
* pertes ni réinvestissements pour cette table (concepts qui n'existent pas côté PE) — en
* repli, valorisations + remboursements (distributions) + frais_operations + documents.
*/
router.get('/:id/export', (req, res, next) => {
try {
const id = Number(req.params.id);
ownedDeal(req, id);
const meta = db.prepare(`
SELECT ipe.*, p.nom AS plateforme_nom, inv.nom AS investisseur_nom
FROM investissements_pe ipe
JOIN plateformes p ON p.id = ipe.plateforme_id
JOIN investisseurs inv ON inv.id = ipe.investisseur_id
WHERE ipe.id = ?
`).get(id);
const valorisations = db.prepare(
'SELECT date_valorisation, valorisation_nette, capital_deploye, plus_value_latente, notes FROM investissements_pe_valorisations WHERE investissement_pe_id = ? ORDER BY date_valorisation ASC, id ASC'
).all(id);
const remboursements = db.prepare(
'SELECT date_remb, capital, cashback, interets_bruts, prelev_sociaux, prelev_forfaitaire, interets_nets, net_recu, statut, distribution_nature, notes FROM remboursements WHERE investissement_pe_id = ? ORDER BY date_remb ASC'
).all(id);
const frais = db.prepare(
'SELECT date_operation, montant, mode_reglement, origine, categorie, notes FROM frais_operations WHERE investissement_pe_id = ? ORDER BY date_operation ASC'
).all(id);
const documents = db.prepare(
"SELECT * FROM documents WHERE user_id = ? AND entity_type = 'investissement_pe' AND entity_id = ? ORDER BY created_at ASC"
).all(req.user.id, id);
// Fichiers du zip + métadonnées correspondantes dans le manifeste — même logique de
// dédoublonnage des noms que l'export crowdlending (cf. investissements.js).
const usedNames = new Set();
const documentsMeta = [];
const entries = [];
for (const doc of documents) {
const baseLabel = sanitizeZipPart(doc.nom_affichage);
let finalName = `${baseLabel}.${doc.extension}`;
let n = 2;
while (usedNames.has(finalName)) { finalName = `${baseLabel} (${n}).${doc.extension}`; n++; }
usedNames.add(finalName);
const zipPath = `documents/${finalName}`;
documentsMeta.push({
nom_affichage: doc.nom_affichage,
nom_original: doc.nom_original,
extension: doc.extension,
mime_type: doc.mime_type,
taille_octets: doc.taille_octets,
categorie: doc.categorie,
zip_path: zipPath,
});
const filePath = path.join(docsDir, doc.filename);
if (fs.existsSync(filePath)) entries.push({ name: zipPath, data: fs.readFileSync(filePath) });
}
const manifest = {
version: '1.0',
type: 'dossier_investissement_pe',
exported_at: new Date().toISOString(),
investissement_pe: {
nom_deal: meta.nom_deal,
strategie: meta.strategie,
date_souscription: meta.date_souscription,
duree_mois: meta.duree_mois,
montant_investi: meta.montant_investi,
capital_investissable: meta.capital_investissable,
objectif: meta.objectif,
statut: meta.statut,
reference: meta.reference,
notes: meta.notes,
},
plateforme: { nom: meta.plateforme_nom },
investisseur: { nom: meta.investisseur_nom },
valorisations,
remboursements,
frais_operations: frais,
documents: documentsMeta,
};
entries.unshift({ name: 'manifest.json', data: JSON.stringify(manifest, null, 2) });
const zipBuf = createZip(entries);
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
const filename = `Dossier_PE_${id}_${ts}.zip`;
res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.send(zipBuf);
} catch (e) { next(e); }
});
router.put('/:id', requireInvestisseur, (req, res, next) => {
try {
const id = Number(req.params.id);