136 lines
5.8 KiB
JavaScript
136 lines
5.8 KiB
JavaScript
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;
|