import { Router } from 'express'; import multer from 'multer'; import xlsx from 'xlsx'; import path from 'node:path'; import fs from 'node:fs'; import db from '../db/index.js'; import { HttpError } from '../middleware/errorHandler.js'; const UPLOAD_DIR = process.env.UPLOAD_DIR || path.resolve('./uploads'); fs.mkdirSync(UPLOAD_DIR, { recursive: true }); const upload = multer({ dest: UPLOAD_DIR, limits: { fileSize: 5 * 1024 * 1024 } }); const router = Router(); /* ── GET /api/garanties ──────────────────────────────────────── */ router.get('/', (req, res) => { const rows = db.prepare( 'SELECT * FROM garantie_types WHERE user_id = ? ORDER BY ordre, id' ).all(req.user.id); res.json(rows); }); /* ── POST /api/garanties ─────────────────────────────────────── */ router.post('/', (req, res, next) => { try { const { libelle, description, ordre } = req.body || {}; if (!libelle?.trim()) throw new HttpError(400, 'libelle est requis'); const r = db.prepare( 'INSERT INTO garantie_types (user_id, libelle, description, ordre) VALUES (?,?,?,?)' ).run(req.user.id, libelle.trim(), description?.trim() || null, Number(ordre ?? 0)); res.status(201).json(db.prepare('SELECT * FROM garantie_types WHERE id = ?').get(r.lastInsertRowid)); } catch (e) { next(e); } }); /* ── PUT /api/garanties/:id ──────────────────────────────────── */ router.put('/:id', (req, res, next) => { try { const row = db.prepare('SELECT id FROM garantie_types WHERE id = ? AND user_id = ?') .get(req.params.id, req.user.id); if (!row) throw new HttpError(404, 'Garantie introuvable'); const { libelle, description, ordre } = req.body || {}; if (!libelle?.trim()) throw new HttpError(400, 'libelle est requis'); db.prepare( 'UPDATE garantie_types SET libelle=?, description=?, ordre=? WHERE id=?' ).run(libelle.trim(), description?.trim() || null, Number(ordre ?? 0), req.params.id); res.json(db.prepare('SELECT * FROM garantie_types WHERE id = ?').get(req.params.id)); } catch (e) { next(e); } }); /* ── DELETE /api/garanties/:id ───────────────────────────────── */ router.delete('/:id', (req, res, next) => { try { const row = db.prepare('SELECT id FROM garantie_types WHERE id = ? AND user_id = ?') .get(req.params.id, req.user.id); if (!row) throw new HttpError(404, 'Garantie introuvable'); db.prepare('DELETE FROM garantie_types WHERE id = ?').run(req.params.id); res.json({ deleted: true }); } catch (e) { next(e); } }); /* ── POST /api/garanties/import ───────────────────────────────── Accepte un fichier .xlsx / .csv / .json Colonnes reconnues : libelle (obligatoire), description, ordre Comportement : upsert sur libelle (insensible à la casse) ─────────────────────────────────────────────────────────────── */ router.post('/import', upload.single('file'), (req, res, next) => { try { if (!req.file) throw new HttpError(400, 'Aucun fichier reçu'); const ext = path.extname(req.file.originalname).toLowerCase(); let rows; if (ext === '.json') { const content = fs.readFileSync(req.file.path, 'utf8'); let parsed; try { parsed = JSON.parse(content); } catch { throw new HttpError(400, 'Fichier JSON invalide'); } rows = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.garanties) ? parsed.garanties : null; if (!rows) throw new HttpError(400, 'Le JSON doit être un tableau ou contenir une clé "garanties"'); } else { const wb = xlsx.readFile(req.file.path, { cellDates: true }); const ws = wb.Sheets[wb.SheetNames[0]]; rows = xlsx.utils.sheet_to_json(ws, { defval: null, raw: false }); } try { fs.unlinkSync(req.file.path); } catch { /* noop */ } if (!rows.length) return res.json({ inserted: 0, updated: 0, skipped: 0, errors: [] }); // Normalise les clés (insensible à la casse et aux espaces) const norm = (obj) => { const out = {}; for (const [k, v] of Object.entries(obj)) out[k.toLowerCase().trim()] = v; return out; }; let inserted = 0, updated = 0, skipped = 0; const errors = []; const tx = db.transaction(() => { for (let i = 0; i < rows.length; i++) { const r = norm(rows[i]); const libelle = String(r.libelle ?? r['libellé'] ?? r.label ?? '').trim(); if (!libelle) { skipped++; errors.push({ row: i + 2, error: 'libelle vide — ligne ignorée' }); continue; } const description = String(r.description ?? r.desc ?? '').trim() || null; const ordre = parseInt(r.ordre ?? r.order ?? 0, 10) || 0; 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(description, ordre, existing.id); updated++; } else { db.prepare( 'INSERT INTO garantie_types (user_id, libelle, description, ordre) VALUES (?,?,?,?)' ).run(req.user.id, libelle, description, ordre); inserted++; } } }); tx(); res.json({ inserted, updated, skipped, total: rows.length, errors: errors.slice(0, 20) }); } catch (e) { next(e); } }); export default router;