Initial commit
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user