From 611dfe82f021ecf6d2d3d648314ea8847b2f701b Mon Sep 17 00:00:00 2001 From: Olivier Date: Sat, 13 Jun 2026 22:03:10 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20export/import=20ZIP=20r=C3=A9f=C3=A9ren?= =?UTF-8?q?tiel=20inclut=20garantie=5Ftypes=20+=20referentiel=5Fnotation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/routes/referentiel.js | 86 ++++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 12 deletions(-) diff --git a/backend/src/routes/referentiel.js b/backend/src/routes/referentiel.js index 14adb9b..6059639 100644 --- a/backend/src/routes/referentiel.js +++ b/backend/src/routes/referentiel.js @@ -281,7 +281,7 @@ const zipUpload = multer({ }); // ── GET /api/referentiel/export — export tout le référentiel ─────────────── -router.get('/export', (_req, res, next) => { +router.get('/export', (req, res, next) => { try { let rows = db.prepare('SELECT * FROM plateformes_referentiel ORDER BY nom').all(); rows = attachCatsInv(rows); @@ -290,7 +290,7 @@ router.get('/export', (_req, res, next) => { const entries = []; const manifest = { - version: '1.0', + version: '1.1', app: 'crowdlending', exported_at: new Date().toISOString(), count: rows.length, @@ -298,6 +298,22 @@ router.get('/export', (_req, res, next) => { }; 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 => ({ nom: r.nom, url: r.url, @@ -333,10 +349,17 @@ router.get('/export', (_req, res, next) => { icone_filename: r.icone_filename, categories_inv: (r.categories_inv || []).map(c => c.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) }); + // 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 for (const r of rows) { 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); } + // 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 for (const fname of [p.logo_filename, p.icone_filename]) { if (!fname || !imageMap[fname]) continue; @@ -552,6 +595,34 @@ router.post('/import-zip', zipUpload.single('file'), async (req, res, next) => { }); 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 }); } catch (e) { next(e); } }); @@ -1000,13 +1071,4 @@ router.put('/notation/:notationId', (req, res, next) => { }); // DELETE /api/referentiel/notation/:notationId -router.delete('/notation/:notationId', (req, res, next) => { - 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; +rou \ No newline at end of file