feat: export/import ZIP référentiel inclut garantie_types + referentiel_notation

This commit is contained in:
2026-06-13 22:03:10 +02:00
parent c987ef3f42
commit 611dfe82f0
+74 -12
View File
@@ -281,7 +281,7 @@ const zipUpload = multer({
}); });
// ── GET /api/referentiel/export — export tout le référentiel ─────────────── // ── GET /api/referentiel/export — export tout le référentiel ───────────────
router.get('/export', (_req, res, next) => { router.get('/export', (req, res, next) => {
try { try {
let rows = db.prepare('SELECT * FROM plateformes_referentiel ORDER BY nom').all(); let rows = db.prepare('SELECT * FROM plateformes_referentiel ORDER BY nom').all();
rows = attachCatsInv(rows); rows = attachCatsInv(rows);
@@ -290,7 +290,7 @@ router.get('/export', (_req, res, next) => {
const entries = []; const entries = [];
const manifest = { const manifest = {
version: '1.0', version: '1.1',
app: 'crowdlending', app: 'crowdlending',
exported_at: new Date().toISOString(), exported_at: new Date().toISOString(),
count: rows.length, count: rows.length,
@@ -298,6 +298,22 @@ router.get('/export', (_req, res, next) => {
}; };
entries.push({ name: 'manifest.json', data: JSON.stringify(manifest, null, 2) }); entries.push({ name: 'manifest.json', data: JSON.stringify(manifest, null, 2) });
// Attach referentiel_notation to each platform
const notationByRef = {};
const notRows = db.prepare('SELECT * FROM referentiel_notation ORDER BY referentiel_id, ordre, id').all();
for (const n of notRows) {
if (!notationByRef[n.referentiel_id]) notationByRef[n.referentiel_id] = [];
notationByRef[n.referentiel_id].push({
nom: n.nom,
type: n.type,
valeurs: n.valeurs ? JSON.parse(n.valeurs) : null,
min_val: n.min_val,
max_val: n.max_val,
description: n.description,
ordre: n.ordre,
});
}
const dataRows = rows.map(r => ({ const dataRows = rows.map(r => ({
nom: r.nom, nom: r.nom,
url: r.url, url: r.url,
@@ -333,10 +349,17 @@ router.get('/export', (_req, res, next) => {
icone_filename: r.icone_filename, icone_filename: r.icone_filename,
categories_inv: (r.categories_inv || []).map(c => c.nom), categories_inv: (r.categories_inv || []).map(c => c.nom),
secteurs_inv: (r.secteurs_inv || []).map(s => s.nom), secteurs_inv: (r.secteurs_inv || []).map(s => s.nom),
notation: notationByRef[r.id] || [],
})); }));
entries.push({ name: 'data.json', data: JSON.stringify(dataRows, null, 2) }); entries.push({ name: 'data.json', data: JSON.stringify(dataRows, null, 2) });
// Types de garanties du user courant
const garanties = db.prepare(
'SELECT libelle, description, ordre FROM garantie_types WHERE user_id = ? ORDER BY ordre, id'
).all(req.user.id);
entries.push({ name: 'garanties.json', data: JSON.stringify(garanties, null, 2) });
// Include logo / icon files // Include logo / icon files
for (const r of rows) { for (const r of rows) {
for (const fname of [r.logo_filename, r.icone_filename]) { for (const fname of [r.logo_filename, r.icone_filename]) {
@@ -542,6 +565,26 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => {
db.prepare('INSERT OR IGNORE INTO referentiel_secteurs_inv (referentiel_id, secteur_id) VALUES (?,?)').run(refId, id); db.prepare('INSERT OR IGNORE INTO referentiel_secteurs_inv (referentiel_id, secteur_id) VALUES (?,?)').run(refId, id);
} }
// Sync referentiel_notation
if (Array.isArray(p.notation) && p.notation.length) {
db.prepare('DELETE FROM referentiel_notation WHERE referentiel_id = ?').run(refId);
for (const n of p.notation) {
db.prepare(`
INSERT INTO referentiel_notation (referentiel_id, nom, type, valeurs, min_val, max_val, description, ordre)
VALUES (?,?,?,?,?,?,?,?)
`).run(
refId,
(n.nom || '').trim(),
n.type || 'etoiles',
n.valeurs ? JSON.stringify(Array.isArray(n.valeurs) ? n.valeurs : [n.valeurs]) : null,
n.min_val ?? null,
n.max_val ?? null,
n.description ?? null,
n.ordre ?? 0,
);
}
}
// Write logo / icon images from ZIP // Write logo / icon images from ZIP
for (const fname of [p.logo_filename, p.icone_filename]) { for (const fname of [p.logo_filename, p.icone_filename]) {
if (!fname || !imageMap[fname]) continue; if (!fname || !imageMap[fname]) continue;
@@ -552,6 +595,34 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => {
}); });
tx(); tx();
// ── Import garanties ──────────────────────────────────────────────────────
const garantiesEntry = zipEntries.find(e => e.name === 'garanties.json');
if (garantiesEntry) {
const garanties = JSON.parse(garantiesEntry.data.toString('utf8'));
if (Array.isArray(garanties) && garanties.length) {
const gtx = db.transaction(() => {
for (const g of garanties) {
const libelle = (g.libelle || '').trim();
if (!libelle) continue;
const existing = db.prepare(
'SELECT id FROM garantie_types WHERE user_id = ? AND LOWER(libelle) = LOWER(?)'
).get(req.user.id, libelle);
if (existing) {
db.prepare(
'UPDATE garantie_types SET description=?, ordre=? WHERE id=?'
).run(g.description ?? null, g.ordre ?? 0, existing.id);
} else {
db.prepare(
'INSERT INTO garantie_types (user_id, libelle, description, ordre) VALUES (?,?,?,?)'
).run(req.user.id, libelle, g.description ?? null, g.ordre ?? 0);
}
}
});
gtx();
}
}
res.json({ ok: true, created, updated, total: platforms.length }); res.json({ ok: true, created, updated, total: platforms.length });
} catch (e) { next(e); } } catch (e) { next(e); }
}); });
@@ -1000,13 +1071,4 @@ router.put('/notation/:notationId', (req, res, next) => {
}); });
// DELETE /api/referentiel/notation/:notationId // DELETE /api/referentiel/notation/:notationId
router.delete('/notation/:notationId', (req, res, next) => { rou
try {
const existing = db.prepare('SELECT id FROM referentiel_notation WHERE id = ?').get(req.params.notationId);
if (!existing) throw new HttpError(404, 'Critere introuvable');
db.prepare('DELETE FROM referentiel_notation WHERE id = ?').run(req.params.notationId);
res.json({ deleted: true });
} catch (e) { next(e); }
});
export default router;