
----------------------------------------------------
server/server.js

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const dotenv = require('dotenv');
const path = require('path');
const fs = require('fs');

dotenv.config();

const app = express();
const PORT = process.env.PORT || 3000;

// ================================================================
// MIDDLEWARES
// ================================================================
app.set('trust proxy', true);
app.use(helmet({ contentSecurityPolicy: false }));
app.use(cors({ origin: true, credentials: true }));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));

// Service des fichiers statiques (pour Angular)
app.use(express.static(path.join(__dirname, '../public')));

// Logger
app.use((req, res, next) => {
    console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`);
    next();
});

// ================================================================
// CHARGER LES CONFIGS
// ================================================================
let appConfigs = {};
try {
    appConfigs.countries = JSON.parse(fs.readFileSync(path.join(__dirname, '../config/countries.json'), 'utf8'));
    appConfigs.currencies = JSON.parse(fs.readFileSync(path.join(__dirname, '../config/currencies.json'), 'utf8'));
    appConfigs.taresRules = JSON.parse(fs.readFileSync(path.join(__dirname, '../config/tares_codes.json'), 'utf8'));
		
    // [FLAG] Vérification de la configuration critique
    const openRouterApiKey = process.env.OPENROUTER_API_KEY;
    if (!openRouterApiKey || openRouterApiKey.length < 10) {
        console.warn(`[FLAG-INIT-WARN] OPENROUTER_API_KEY semble manquant ou trop court.`);
    } else {
        console.log(`[FLAG-INIT-OK] OPENROUTER_API_KEY chargée (${openRouterApiKey.substring(0, 10)}...).`);
    }
		
    // On attache les configs à l'objet 'app' pour qu'il soit accessible dans les routes
    app.appConfigs = appConfigs;
    console.log('✓ Fichiers de configuration chargés');
} catch (error) {
    console.error('⚠ Erreur chargement config:', error.message);
}

// ================================================================
// ROUTES DE L'API
// ================================================================
// AJOUT : Charger les routes depuis le fichier dédié
const apiRoutes = require('./routes/api');
app.use('/api', apiRoutes);

// ================================================================
// ROUTE FALLBACK POUR ANGULAR (Single Page Application)
// ================================================================
// Cette route doit être après les routes API
app.get('*', (req, res) => {
    res.sendFile(path.join(__dirname, '../public/index.html'));
});

// ================================================================
// GESTION D'ERREURS
// ================================================================
app.use((err, req, res, next) => {
    console.error('Erreur:', err.stack);
    res.status(err.status || 500).json({
        error: process.env.NODE_ENV === 'production'
            ? 'Erreur interne du serveur'
            : err.message
    });
});

app.use((req, res) => {
    res.status(404).json({ error: 'Route API non trouvée: ' + req.path });
});

// ================================================================
// DÉMARRAGE
// ================================================================
app.listen(PORT, '127.0.0.1', () => {
    console.log(`╔════════════════════════════════════════════════════════════╗`);
    console.log(`║  Backend e-dec démarré sur http://127.0.0.1:${PORT}        ║`);
    console.log(`╚════════════════════════════════════════════════════════════╝`);
});

process.on('SIGTERM', () => {
    console.log('SIGTERM reçu, arrêt gracieux...');
    process.exit(0);
});
----------------------------------------------------
server/routes/api.js

const express = require('express');
const router = express.Router();
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const { body, validationResult } = require('express-validator');
const { pool } = require('../db');
const ocrService = require('../services/ocrService');
const taresClassifier = require('../services/taresClassifier');
const edecGenerator = require('../services/edecGenerator');
const exchangeRateService = require('../services/exchangeRateService');
const { v4: uuidv4 } = require('uuid');

// Multer
const upload = multer({
  dest: path.join(__dirname, '../../public/uploads'),
  limits: { fileSize: 10 * 1024 * 1024 },
  fileFilter: (req, file, cb) => {
    const allowed = ['image/jpeg', 'image/png', 'application/pdf'];
    if (allowed.includes(file.mimetype)) cb(null, true);
    else cb(new Error('Formats supportés: JPG, PNG, PDF'));
  }
});

// =========================================================================
// FONCTIONS DE RECHERCHE BRAND CODE (Optimisation : intégrées ici)
// =========================================================================

/**
 * Recherche le brand_code par le nom de la marque.
 */
async function getBrandCodeByBrandName(brandName) {
    if (!brandName) return null;
    const cleanedBrandName = brandName.trim().toUpperCase();
    const query = `SELECT brand_code FROM brand_codes WHERE brand_name = ?`;
    const [rows] = await pool.execute(query, [cleanedBrandName]);
    return rows.length > 0 ? rows[0].brand_code : null;
}

/**
 * Récupère la liste complète des brand_codes.
 */
async function getAllBrandCodes() {
    const query = `SELECT brand_code, brand_name FROM brand_codes ORDER BY brand_name`;
    const [rows] = await pool.execute(query);
    return rows;
}


// Health
router.get('/health', (req, res) => {
  res.json({
    status: 'OK',
    timestamp: new Date().toISOString(),
    service: 'e-dec Node.js Backend',
    version: '1.1.0'
  });
});

// Reference data
// CORRECTION : Utiliser req.app.appConfigs pour accéder aux configurations globales de l'application Express.
router.get('/countries', (req, res) => res.json(req.app.appConfigs.countries || {}));
router.get('/currencies', (req, res) => res.json(req.app.appConfigs.currencies || {}));

// Exchange rates
router.get('/exchange-rates', async (req, res) => {
  try {
    const rates = await exchangeRateService.getCurrentRates();
    res.json(rates);
  } catch (e) {
    res.status(500).json({ error: e.message });
  }
});

// Currency conversion
router.post('/convert-currency', [
  body('amount').isFloat({ min: 0 }),
  body('from').isString().isLength({ min: 3, max: 3 }),
  body('to').optional().isString().isLength({ min: 3, max: 3 })
], async (req, res) => {
  try {
    const errors = validationResult(req);
    if (!errors.isEmpty()) return res.status(400).json({ error: 'Paramètres invalides', details: errors.array() });
    const { amount, from, to = 'CHF' } = req.body;
    const converted = await exchangeRateService.convert(Number(amount), from, to);
    res.json({ converted });
  } catch (e) {
    res.status(500).json({ error: e.message });
  }
});

// =========================================================================
// NOUVELLE ROUTE : Brand Code Lookup
// =========================================================================
router.post('/brand-codes/lookup', [
    body('brand').trim().notEmpty().withMessage("Le nom de la marque est requis.")
], async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(400).json({ error: 'Champs invalides', details: errors.array() });
    }

    const { brand } = req.body;

    try {
        const brandCode = await getBrandCodeByBrandName(brand); // <-- Appel direct

        if (brandCode) {
            return res.json({ brandCode });
        }

        const options = await getAllBrandCodes(); // <-- Appel direct
        
        if (options.length === 0) {
            return res.status(404).json({ message: "Liste des codes de marque non disponible." });
        }

        return res.json({ options });

    } catch (error) {
        console.error('Erreur lors de la recherche du code de marque:', error);
        return res.status(500).json({ error: "Erreur interne du serveur lors de la recherche du code de marque." });
    }
});
// =========================================================================

// OCR
router.post('/ocr', upload.single('registration_card'), async (req, res) => {
  const filePath = req.file?.path;
  try {
    if (!filePath) return res.status(400).json({ error: 'Aucun fichier fourni' });
    const extractedData = await ocrService.extractFromFile(filePath, req.file.mimetype);
    res.json(extractedData);
  } catch (error) {
    console.error('Erreur OCR complète:', error);
    res.status(500).json({ 
      error: 'Erreur OCR', 
      details: error.message,
      hint: 'Vérifiez que votre clé API OpenRouter est valide et que le fichier est lisible'
    });
  } finally {
    if (filePath && fs.existsSync(filePath)) try { fs.unlinkSync(filePath); } catch (_) {}
  }
});

// Validate VIN + country
router.post('/validate-vin', [
  body('vin').isString().isLength({ min: 17, max: 17 })
], async (req, res) => {
  const { vin } = req.body;
  const valid = /^[A-HJ-NPR-Z0-9]{17}$/.test((vin || '').toUpperCase());
  let country = null;
  try {
    if (valid) {
      country = await taresClassifier.getCountryFromVIN_DB(vin);
    }
    res.json({ valid, country });
  } catch (e) {
    res.status(500).json({ error: e.message });
  }
});

// Classification TARES
router.post('/classify-vehicle', async (req, res) => {
  try {
    // CORRECTION : Utiliser req.app.appConfigs pour accéder aux configurations globales.
    const classification = await taresClassifier.classify(req.body, req.app.appConfigs.taresRules);
    res.json(classification);
  } catch (error) {
    console.error('Erreur classification:', error);
    res.status(500).json({ error: error.message });
  }
});

// Generate Import XML + persist
router.post('/generate-import', [
  body('user_name').isString().notEmpty(),
  body('user_zip').isString().notEmpty(),
  body('user_city').isString().notEmpty(),
  body('dispatch_country').isString().isLength({ min: 2, max: 2 }),
  body('transport_mode').isString().isIn(['9', '2', '3', '4', '7', '8']),
  body('purchase_price').isFloat({ min: 0 }),
  body('purchase_currency').isString().isLength({ min: 3, max: 3 }),
  body('vin').isString().isLength({ min: 17, max: 17 }),
  body('brand').isString().notEmpty(),
	body('brand_code').optional()
  .isString().isLength({ min: 3, max: 3 }).isNumeric().withMessage("Le brand_code doit être une chaîne de 3 chiffres uniquement (ex. : 123)."),
  body('model').isString().notEmpty(),
  body('weight_empty').isInt({ min: 1 }),
  body('weight_total').isInt({ min: 1 }),
], async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) return res.status(400).json({ error: 'Champs invalides', details: errors.array() });

  const conn = await pool.getConnection();
  const txId = uuidv4();
  try {
    await conn.beginTransaction();

    // Convert to CHF
    const sum = Number(req.body.purchase_price || 0) + Number(req.body.transport_cost || 0) + Number(req.body.other_costs || 0);
    const statistical_value_chf = await exchangeRateService.convert(sum, req.body.purchase_currency, 'CHF');

    // VAT value (for e-dec vatValue): vehicle cost + import costs in CHF
    const vat_value_chf = statistical_value_chf;

    // Determine TARES if not present
    let tar = req.body.commodity_code;
    let statKey = req.body.statistical_key;
    if (!tar || !statKey) {
      const cls = await taresClassifier.classify(req.body, req.app.appConfigs.taresRules);
      tar = tar || cls.commodity_code;
      statKey = statKey || cls.statistical_key || '911';
    }
		
		// LOGIQUE DE RÉCUPÉRATION DU BRAND CODE
		let finalBrandCode = req.body.brand_code;
		
		if (!finalBrandCode && req.body.brand) {
				// Tente la recherche automatique si le code n'a pas été fourni par le client
				const autoCode = await getBrandCodeByBrandName(req.body.brand); // <-- Appel direct
				finalBrandCode = autoCode;
		}

    // Origin from VIN
    let origin = await taresClassifier.getCountryFromVIN_DB(req.body.vin);
    origin = origin || req.body.dispatch_country || 'FR';

    // Persist declaration
    const [declRes] = await conn.execute(`
      INSERT INTO declarations (uuid, declaration_type, user_name, user_firstname, user_address, user_zip, user_city, user_country, user_ide, language, status)
      VALUES (?, 'IMPORT', ?, ?, ?, ?, ?, 'CH', ?, ?, 'draft')
    `, [
      txId,
      req.body.user_name || '',
      req.body.user_firstname || null,
      req.body.user_address || null,
      req.body.user_zip || '',
      req.body.user_city || '',
      req.body.user_ide || null,
      (req.body.language || 'fr').toLowerCase()
    ]);
    const declarationId = declRes.insertId;

    await conn.execute(`
      INSERT INTO import_details (declaration_id, dispatch_country, transport_mode, is_relocation, purchase_price, purchase_currency, transport_cost, other_costs, statistical_value, vat_value)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `, [
      declarationId,
      req.body.dispatch_country,
      req.body.transport_mode,
      req.body.is_relocation ? 1 : 0,
      Number(req.body.purchase_price || 0),
      req.body.purchase_currency,
      Number(req.body.transport_cost || 0),
      Number(req.body.other_costs || 0),
      statistical_value_chf,
      vat_value_chf
    ]);

    await conn.execute(`
      INSERT INTO vehicles (declaration_id, vin, brand, model, year, cylinder_capacity, fuel_type, weight_empty, weight_total, tares_code, statistical_key, country_origin, brand_code)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `, [
      declarationId,
      req.body.vin.toUpperCase(),
      req.body.brand,
      req.body.model,
      req.body.year || null,
      req.body.cylinder_capacity || null,
      req.body.fuel_type || null,
      req.body.weight_empty,
      req.body.weight_total,
      tar,
      statKey,
      origin,
      finalBrandCode
    ]);

    // Build XML (with all business rules)
    const xml = await edecGenerator.generateImport({
      ...req.body,
      commodity_code: tar,
      statistical_key: statKey,
      origin_country: origin,
      statistical_value_chf,
      vat_value_chf,
			brand_code: finalBrandCode
    });

    // Sauvegarder le XML dans un fichier
    const xmlDir = path.join(__dirname, '../../xml');
    if (!fs.existsSync(xmlDir)) {
      fs.mkdirSync(xmlDir, { recursive: true });
    }
    const xmlFilePath = path.join(xmlDir, `${txId}.xml`);
    fs.writeFileSync(xmlFilePath, xml, 'utf8');

    await conn.execute(`UPDATE declarations SET xml_content = ?, xml_file_path = ?, status = 'generated' WHERE id = ?`, [xml, xmlFilePath, declarationId]);

    await conn.commit();

    res.set('Content-Type', 'application/xml');
    res.set('Content-Disposition', `attachment; filename="edec-import-${Date.now()}.xml"`);
    res.send(xml);
  } catch (e) {
    await conn.rollback();
    console.error('Erreur génération import:', e);
    res.status(500).json({ error: e.message });
  } finally {
    conn.release();
  }
});

// Generate Export XML + persist
router.post('/generate-export', [
  body('user_name').isString().notEmpty(),
  body('user_zip').isString().notEmpty(),
  body('user_city').isString().notEmpty(),
  body('destination_country').isString().isLength({ min: 2, max: 2 }),
  body('vin').isString().isLength({ min: 17, max: 17 }),
  body('brand').isString().notEmpty(),
  body('model').isString().notEmpty(),
], async (req, res) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) return res.status(400).json({ error: 'Champs invalides', details: errors.array() });

  const conn = await pool.getConnection();
  const txId = uuidv4();
  try {
    await conn.beginTransaction();

    const [declRes] = await conn.execute(`
      INSERT INTO declarations (uuid, declaration_type, user_name, user_firstname, user_address, user_zip, user_city, user_country, user_ide, language, status)
      VALUES (?, 'EXPORT', ?, ?, ?, ?, ?, 'CH', ?, ?, 'draft')
    `, [
      txId,
      req.body.user_name || '',
      req.body.user_firstname || null,
      req.body.user_address || null,
      req.body.user_zip || '',
      req.body.user_city || '',
      req.body.user_ide || null,
      (req.body.language || 'fr').toLowerCase()
    ]);
    const declarationId = declRes.insertId;

    await conn.execute(`
      INSERT INTO export_details (declaration_id, destination_country, taxation_code)
      VALUES (?, ?, ?)
    `, [
      declarationId,
      req.body.destination_country,
      req.body.taxation_code || null
    ]);

    await conn.execute(`
      INSERT INTO vehicles (declaration_id, vin, brand, model, year, cylinder_capacity, fuel_type, weight_empty, weight_total, tares_code, statistical_key, country_origin, brand_code)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `, [
      declarationId,
      req.body.vin.toUpperCase(),
      req.body.brand,
      req.body.model,
      req.body.year || null,
      req.body.cylinder_capacity || null,
      req.body.fuel_type || null,
      req.body.weight_empty || null,
      req.body.weight_total || null,
      req.body.commodity_code || '8703.9060',
      req.body.statistical_key || '911',
      req.body.origin_country || null,
      req.body.brand_code || null
    ]);

    const xml = await edecGenerator.generateExport(req.body);

    await conn.execute(`UPDATE declarations SET xml_content = ?, status = 'generated' WHERE id = ?`, [xml, declarationId]);

    await conn.commit();

    res.set('Content-Type', 'application/xml');
    res.set('Content-Disposition', `attachment; filename="edec-export-${Date.now()}.xml"`);
    res.send(xml);
  } catch (e) {
    await conn.rollback();
    console.error('Erreur génération export:', e);
    res.status(500).json({ error: e.message });
  } finally {
    conn.release();
  }
});

// Soumission automatique à e-dec
router.post('/submit-to-edec', async (req, res) => {
  const { sessionToken, xmlPath } = req.body;
  
  if (!sessionToken || !xmlPath) {
    return res.status(400).json({ error: 'sessionToken et xmlPath requis' });
  }

  const edecSubmissionService = require('../services/edecSubmissionService');

  try {
    const result = await edecSubmissionService.submitDeclaration(xmlPath, sessionToken);
    res.json(result);
  } catch (error) {
    console.error('Erreur soumission e-dec:', error);
    res.status(500).json({ 
      error: 'Erreur lors de la soumission automatique', 
      details: error.message 
    });
  }
});

module.exports = router;
----------------------------------------------------
server/services/edecGenerator.js

const edecGeneratorImport = require('./edecGeneratorImport');
const edecGeneratorExport = require('./edecGeneratorExport');

class EdecGenerator {
  generateImport(data) {
    return edecGeneratorImport.generateImport(data);
  }
  generateExport(data) {
    return edecGeneratorExport.generateExport(data);
  }
}

module.exports = new EdecGenerator();
----------------------------------------------------
server/services/edecSubmissionService.js

const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
const { pool } = require('../db');

class EdecSubmissionService {
    constructor() {
        this.baseUrl = 'https://e-dec-web.ezv.admin.ch/webdec/main.xhtml';
        this.downloadDir = path.join(__dirname, '../../edecpdf');
        
        if (!fs.existsSync(this.downloadDir)) {
            fs.mkdirSync(this.downloadDir, { recursive: true });
        }
    }

    async submitDeclaration(xmlFilePath, sessionToken) {
        let browser = null;
        let declarationNumber = null;

        try {
            console.log(`[EDEC Submit] Démarrage soumission pour token: ${sessionToken}`);

            browser = await chromium.launch({
                headless: true,
                args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
            });

            const context = await browser.newContext({
                acceptDownloads: true,
                userAgent: 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
            });

            const page = await context.newPage();

            console.log('[EDEC Submit] Navigation vers le portail...');
            await page.goto(this.baseUrl, { waitUntil: 'networkidle', timeout: 60000 });
            await page.waitForTimeout(2000);

            console.log('[EDEC Submit] Clic sur "Charger la déclaration"...');
            await page.click('a#mainform\\:loadDeclaration');
            await page.waitForTimeout(2000);

            console.log('[EDEC Submit] Upload du fichier XML...');
            const fileInput = await page.locator('input[type="file"][name="upload"]');
            await fileInput.setInputFiles(xmlFilePath);
            await page.waitForTimeout(3000);

            console.log('[EDEC Submit] Clic sur le bouton OK...');
            await page.click('a#mainform\\:confirmButton');
            await page.waitForTimeout(3000);

            console.log('[EDEC Submit] Clic sur le bouton Envoyer...');
            await page.click('a#j_id49\\:j_id69');
            await page.waitForTimeout(5000);

            console.log('[EDEC Submit] Extraction du numéro de déclaration...');
            const declarationElement = await page.locator('span.iceOutFrmt:has-text("La déclaration en douane a été établie")').first();
            const declarationText = await declarationElement.textContent();
            const match = declarationText.match(/(\d{20})/);
            
            if (!match) {
                throw new Error('Numéro de déclaration introuvable dans la réponse');
            }
            
            declarationNumber = match[1];
            console.log(`[EDEC Submit] Numéro de déclaration: ${declarationNumber}`);

            console.log('[EDEC Submit] Téléchargement Liste d\'importation...');
            const downloadPromise1 = page.waitForEvent('download');
            await page.click('a#mainform\\:j_id86');
            const download1 = await downloadPromise1;
            const listePath = path.join(this.downloadDir, `${sessionToken}_liste_importation.pdf`);
            await download1.saveAs(listePath);
            console.log(`[EDEC Submit] Liste d'importation sauvegardée: ${listePath}`);

            console.log('[EDEC Submit] Téléchargement Bulletin de délivrance...');
            await page.waitForTimeout(1000);
            const bulletinLink = await page.locator('a.iceOutLnk.downloadLink').nth(1);
            const downloadPromise2 = page.waitForEvent('download');
            await bulletinLink.click();
            const download2 = await downloadPromise2;
            const bulletinPath = path.join(this.downloadDir, `${sessionToken}_bulletin_delivrance.pdf`);
            await download2.saveAs(bulletinPath);
            console.log(`[EDEC Submit] Bulletin de délivrance sauvegardé: ${bulletinPath}`);

            console.log('[EDEC Submit] Mise à jour de la base de données...');
            await this.saveToDatabase(sessionToken, declarationNumber, listePath, bulletinPath);

            console.log('[EDEC Submit] Soumission terminée avec succès');
            return {
                success: true,
                declarationNumber,
                listePath,
                bulletinPath
            };

        } catch (error) {
            console.error('[EDEC Submit] Erreur:', error);
            throw error;
        } finally {
            if (browser) {
                await browser.close();
            }
        }
    }

    async saveToDatabase(sessionToken, declarationNumber, listePath, bulletinPath) {
        const conn = await pool.getConnection();
        try {
            await conn.execute(`
                UPDATE declarations 
                SET 
                    declaration_number = ?,
                    liste_importation_path = ?,
                    bulletin_delivrance_path = ?,
                    status = 'submitted',
                    submitted_at = NOW()
                WHERE uuid = ?
            `, [declarationNumber, listePath, bulletinPath, sessionToken]);

        } finally {
            conn.release();
        }
    }
}

module.exports = new EdecSubmissionService();

----------------------------------------------------
server/services/edecGeneratorImport.js

function escapeXml(value) {
  if (value === null || value === undefined) return '';
  return String(value)
    .replace(/&/g, '&amp;').replace(/</g, '&lt;')
    .replace(/>/g, '&gt;').replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}

// invoiceCurrencyType mapping:
// 1=CHF, 2=EUR, 3=USD, 4=Autre monnaie UE, 5=Autre monnaie
function mapInvoiceCurrencyType(currency) {
  if (currency === 'CHF') return 1;
  if (currency === 'EUR') return 2;
  if (currency === 'USD') return 3;

  // Liste des pays de l'UE qui n'utilisent pas l'Euro (BGN, CZK, DKK, HUF, PLN, RON, SEK)
  const euNonEuroCurrencies = ['BGN', 'CZK', 'DKK', 'HUF', 'PLN', 'RON', 'SEK'];
  if (euNonEuroCurrencies.includes(currency)) {
    return 4; // Autre monnaie UE
  }

  return 5; // Autre monnaie
}

class EdecGeneratorImport {
  generateImport(data) {
    // CORRECTION: Remplace 'T' par un espace et 'Z' par ' UTC' pour le format e-dec (YYYY-MM-DD HH:mm:ss.sss UTC)
    const now = new Date().toISOString().replace('T', ' ').replace('Z', ' UTC');
    
    const lang = (data.language || 'fr').toLowerCase();
    const dispatchCountry = data.dispatch_country || 'FR';
    const transportMode = data.transport_mode || '9'; 
    
    // Le nom (raison sociale ou Prénom Nom) est désormais formaté par le controller client
    const importerName = escapeXml(data.user_name || '');
    const importerStreet = escapeXml(data.user_address || '');
    const importerZip = escapeXml(data.user_zip || '');
    const importerCity = escapeXml(data.user_city || '');
    const importerCountry = 'CH';
    const ide = escapeXml(data.user_ide || 'CHE222251936'); // Particulier par défaut

    const consigneeName = escapeXml(data.consignee_name || data.user_name || '');
    const consigneeStreet = escapeXml(data.consignee_address || data.user_address || '');
    const consigneeZip = escapeXml(data.consignee_zip || data.user_zip || '');
    const consigneeCity = escapeXml(data.consignee_city || data.user_city || '');
    const consigneeIde = escapeXml(data.consignee_ide || ide);

    const declarantName = escapeXml(data.declarant_name || data.user_name || '');
    const declarantStreet = escapeXml(data.declarant_address || data.user_address || '');
    const declarantZip = escapeXml(data.declarant_zip || data.user_zip || '');
    const declarantCity = escapeXml(data.declarant_city || data.user_city || '');
    const declarantIde = escapeXml(data.declarant_ide || '');

    const description = escapeXml(`${data.brand || ''} ${data.model || ''} ${data.year || ''}`.trim());
    const commodityCode = escapeXml(data.commodity_code || '8703.9060'); 
    const statisticalCode = escapeXml(data.statistical_key || '911'); 
    
    // Les masses doivent être des nombres entiers (arrondi par précaution)
    const grossMass = Math.round(Number(data.weight_total || 0));
    const netMass = Math.round(Number(data.weight_empty || 0));

    // Correction: La valeur statistique et la base TVA sont arrondies à l'ENTIER.
    // statisticalValue (Prix d'achat hors coûts annexes)
    const statisticalValue = Math.round(Number(data.statistical_value_chf || data.statistical_value || 0)); 
    // vatValue (Prix d'achat + coûts annexes)
    const vatValue = Math.round(Number(data.vat_value_chf || data.vatValue || statisticalValue));

    const vin = escapeXml(data.vin || '');
    const brandCode = escapeXml(data.brand_code || '');

    const originCountry = escapeXml(data.origin_country || data.originCountry || dispatchCountry || 'FR');
    const preference = 0; // pas de tarif préférentiel par défaut

    // TVA code: 1 normal, 3 déménagement
    const vatCode = data.is_relocation ? '3' : '1';

    // Additional tax (IVA) RG 660: key 001 (assujetti) ou 002 (exonéré)
    const additionalTaxKey = data.is_iva_exempt ? '002' : '001';
    // Assure que la quantité pour la taxe additionnelle est un entier
    const additionalTaxQty = Math.round(Number(data.additional_tax_value || data.purchase_price || statisticalValue || 0));

    // Utilisation de data.purchase_currency pour déduire le type de monnaie
    const invoiceCurrencyType = mapInvoiceCurrencyType(data.purchase_currency);
    
    // Construction des détails de l'article de marchandise (VIN et Brand Code)
    let goodsItemDetailsXml = '';
    if (vin || brandCode) {
        goodsItemDetailsXml = `
        <goodsItemDetails>
          <goodsItemDetail>
            <GoodsItemDetail>
              <name>2</name>
              <value>${vin}</value>
            </GoodsItemDetail>
            ${brandCode ? `
            <GoodsItemDetail>
              <name>1</name>
              <value>${brandCode}</value>
            </GoodsItemDetail>` : ''}
          </goodsItemDetail>
        </goodsItemDetails>`;
    }

    // Ajout de l'entête XML et utilisation de la variable now corrigée
    const xml = `<?xml version="1.0" encoding="UTF-8"?>
<EdecWeb version="4.0" createdDate="${now}">
  <goodsDeclarationType>
    <serviceType>1</serviceType>
    <declarationType>1</declarationType>
    <language>${lang}</language>
    <dispatchCountry>${dispatchCountry}</dispatchCountry>
    <transportMeans>
      <transportMode>${transportMode}</transportMode>
      <transportationNumber></transportationNumber>
    </transportMeans>
    <transportInContainer>0</transportInContainer>
    <previousDocument/>
    <importer>
      <name>${importerName}</name>
      ${importerStreet ? `<street>${importerStreet}</street>` : ''}
      <postalCode>${importerZip}</postalCode>
      <city>${importerCity}</city>
      <country>${importerCountry}</country>
      <traderIdentificationNumber>${ide}</traderIdentificationNumber>
    </importer>
    <consignee>
      <name>${consigneeName}</name>
      ${consigneeStreet ? `<street>${consigneeStreet}</street>` : ''}
      <postalCode>${consigneeZip}</postalCode>
      <city>${consigneeCity}</city>
      <country>${importerCountry}</country>
      <traderIdentificationNumber>${consigneeIde}</traderIdentificationNumber>
    </consignee>
    <declarant>
      ${declarantIde ? `<traderIdentificationNumber>${declarantIde}</traderIdentificationNumber>` : '<traderIdentificationNumber></traderIdentificationNumber>'}
      <name>${declarantName}</name>
      ${declarantStreet ? `<street>${declarantStreet}</street>` : ''}
      <postalCode>${declarantZip}</postalCode>
      <city>${declarantCity}</city>
      <country>${importerCountry}</country>
    </declarant>
    <business>
      <customsAccount>0</customsAccount>
      <vatAccount>0</vatAccount>
      <vatSuffix>0</vatSuffix>
      <invoiceCurrencyType>${invoiceCurrencyType}</invoiceCurrencyType>
    </business>
    <goodsItem>
      <GoodsItemType>
        <traderItemID>0</traderItemID>
        <description>${description}</description>
        <commodityCode>${commodityCode}</commodityCode>
        <statisticalCode>${statisticalCode}</statisticalCode>
        <grossMass>${grossMass}</grossMass>
        <netMass>${netMass}</netMass>
        <additionalUnit>1</additionalUnit>
        <permitObligation>0</permitObligation>
        <nonCustomsLawObligation>2</nonCustomsLawObligation>
        <statistic>
          <customsClearanceType>1</customsClearanceType>
          <commercialGood>1</commercialGood>
          <statisticalValue>${statisticalValue}</statisticalValue>
          <repair>0</repair>
        </statistic>
        <origin>
          <originCountry>${originCountry}</originCountry>
          <preference>${preference}</preference>
        </origin>
        ${goodsItemDetailsXml}
        <packaging>
          <PackagingType>
            <packagingType>VN</packagingType>
            <quantity>1</quantity>
            <packagingReferenceNumber>${description}</packagingReferenceNumber>
          </PackagingType>
        </packaging>
        <valuation>
          <netDuty>0</netDuty>
          <vatValue>${vatValue}</vatValue>
          <vatCode>${vatCode}</vatCode>
        </valuation>
        <additionalTax>
          <AdditionalTaxType>
            <type>660</type>
            <key>${additionalTaxKey}</key>
            <quantity>${additionalTaxQty}</quantity>
          </AdditionalTaxType>
        </additionalTax>
      </GoodsItemType>
    </goodsItem>
  </goodsDeclarationType>
</EdecWeb>`;

    return xml;
  }
}

module.exports = new EdecGeneratorImport();
----------------------------------------------------
server/services/ocrService.js

const axios = require('axios');
const fs = require('fs');
const sharp = require('sharp');
const path = require('path');

/**
 * Service pour l'OCR (Reconnaissance Optique de Caractères)
 * utilisant l'API OpenRouter (Gemini) pour extraire les données
 * de documents (images ou PDF) de type carte grise/certificat d'immatriculation.
 */
class OcrService {
    constructor() {
        // Initialisation des propriétés de configuration
        this.apiKey = process.env.OPENROUTER_API_KEY;
        this.model = process.env.OPENROUTER_MODEL || 'google/gemini-2.5-flash';
        
        console.log('[OCR Service] Initialisé avec:', {
            hasApiKey: !!this.apiKey,
            apiKeyPreview: this.apiKey ? this.apiKey.substring(0, 10) + '...' : 'MANQUANT',
            model: this.model
        });
    }

    /**
     * Construit le prompt détaillé pour l'extraction de données de carte grise.
     * @returns {string} Le prompt à envoyer au modèle.
     */
    buildExtractionPrompt() {
        return `Tu es un expert en extraction de données de certificats d'immatriculation (cartes grises) européens (Suisse, France, Allemagne, Italie, etc.). 
		Analyse l'image ou le document fourni et retourne UNIQUEMENT un objet JSON strict correspondant au format: 
			{"vin":"VF3LCYHZPFS123456","brand":"Peugeot","model":"308","year":2015,"cylinder_capacity":1560,"fuel_type":"diesel","weight_empty":1245,"weight_total":1870,"type_approval":"e11*2007/46*0009*15"} 
		RÈGLES ET TERMES DE RECHERCHE PAR CHAMP: 
			- vin: 17 caractères alphanumériques. Cherche les champs E, 23, FIN, Chassis N., Fahrgestell-Nr, Telaio n., ou VIN. 
				Supprime les espaces internes pour la valeur finale. 
				IMPORTANT: Les lettres I, O et Q ne sont JAMAIS utilisées dans un VIN (norme ISO 3779).
				Si tu rencontres un I, considère-le comme un 1. Si tu rencontres un O ou un Q, considère-les comme des 0. 
			- brand: D.1, Marke.
			- model: D.3, Modèle, Typ. 
			- year: L'année de la première mise en circulation.
			- Cherche les champs B, 1. Inverkehrsetzung, 1ère mise en circulation, Data di prima immatricolazione. Isole UNIQUEMENT l'année (YYYY). 
			- cylinder_capacity: P.1, Hubraum, Cilindrata, Cylindrée. (Int en cm³) 
			- fuel_type: P.3, type de carburant, Carburant, Kraftstoff, Alimentazione. (essence|diesel|electrique|hybride|hybride_plugin) 
			- weight_empty: G, Leergewicht, Poids à vide, massa a vuoto. (Int en kg) 
			- weight_total: F.2, Gesamtgewicht, Poids total autorisé en charge, Massa massima ammissibile a pieno carico. (Int en kg) 
			- type_approval: K, Réception par type, Typengenehmigung, Approvazione del tipo. 
			Retourne strictement du JSON valide sans commentaire ni texte. Si une valeur est totalement inconnue, utilise: null.`.trim();
    }

    /**
     * Valide et nettoie un numéro VIN.
     * @param {string} vin - Le VIN à valider.
     * @returns {string|null} Le VIN nettoyé ou null si invalide.
     */
    validateVIN(vin) {
        if (!vin) return null;

        // Supprimer tous les espaces et normaliser en majuscules
        let clean = vin.replace(/\s/g, '').toUpperCase().trim();

        // Correction automatique des lettres interdites (ISO 3779: I, O, Q)
        const corrected = clean
            .replace(/I/g, '1')
            .replace(/[OQ]/g, '0');

        if (clean !== corrected) {
            console.warn('[OCR] VIN corrigé automatiquement:', {
                original: vin,
                corrected
            });
        }

        // Vérifie que c'est bien un VIN valide (17 caractères, sans I, O, Q)
        return /^[A-HJ-NPR-Z0-9]{17}$/.test(corrected) ? corrected : null;
    }

    /**
     * Valide l'année.
     * @param {number|string} year - L'année à valider.
     * @returns {number|null} L'année ou null.
     */
    validateYear(year) {
        if (!year) return null;
        const y = parseInt(year, 10);
        return (y >= 1900 && y <= new Date().getFullYear() + 1) ? y : null;
    }

    /**
     * Valide un entier positif.
     * @param {number|string} value - La valeur à valider.
     * @returns {number|null} L'entier ou null.
     */
    validateInteger(value) {
        if (value === null || value === undefined || value === '') return null;
        const int = parseInt(value, 10);
        // Vérifie que c'est un nombre fini et strictement positif
        return Number.isFinite(int) && int > 0 ? int : null;
    }

    /**
     * Normalise le type de carburant.
     * @param {string} fuelType - Le type de carburant brut.
     * @returns {string|null} Le type normalisé ou null.
     */
    normalizeFuelType(fuelType) {
        if (!fuelType) return null;
        const normalized = fuelType.toLowerCase().trim();
        if (normalized.includes('plug')) return 'hybride_plugin';
        if (normalized.includes('hybr')) return 'hybride';
        if (normalized.includes('elec') || normalized.includes('élec')) return 'electrique';
        if (normalized.includes('dies')) return 'diesel';
        if (normalized.includes('ess') || normalized.includes('petrol') || normalized.includes('gasoline')) return 'essence';
        return normalized;
    }

    /**
     * Nettoie et parse la réponse JSON du modèle.
     * @param {string} response - La réponse brute du modèle.
     * @returns {object} L'objet de données parsé et validé.
     */
    parseGeminiJSON(response) {
        console.log('[OCR] ÉTAPE 7a - Parsing JSON, réponse brute:', response);
        // Supprime les balises de code Markdown JSON
        const clean = response.replace(/```json|```/g, '').trim();
        console.log('[OCR] ÉTAPE 7b - Après nettoyage:', clean);

        try {
            const data = JSON.parse(clean);
            console.log('[OCR] ÉTAPE 7c - JSON parsé:', data);

            // Validation et normalisation des champs extraits
            return {
                vin: this.validateVIN(data.vin),
                brand: data.brand || null,
                model: data.model || null,
                year: this.validateYear(data.year),
                cylinder_capacity: this.validateInteger(data.cylinder_capacity),
                fuel_type: this.normalizeFuelType(data.fuel_type),
                weight_empty: this.validateInteger(data.weight_empty),
                weight_total: this.validateInteger(data.weight_total),
                type_approval: data.type_approval || null
            };
        } catch (err) {
            console.error('[OCR] ERREUR ÉTAPE 7 - Parsing JSON:', err);
            throw new Error('Format de réponse OCR invalide: ' + err.message);
        }
    }

    /**
     * Extrait les données d'un fichier (image ou PDF) via l'API OpenRouter.
     * @param {string} filePath - Chemin d'accès au fichier.
     * @param {string} mimeType - Type MIME du fichier.
     * @returns {Promise<object>} Les données extraites.
     */
    async extractFromFile(filePath, mimeType) {
        console.log('[OCR] ÉTAPE 1 - Début extraction:', { filePath, mimeType });

        if (!this.apiKey) {
            throw new Error('OPENROUTER_API_KEY manquante dans .env');
        }

        // Validation simple du type MIME
        const safeMime = ['image/jpeg', 'image/png', 'application/pdf'].includes(mimeType) ? mimeType : 'image/jpeg';
        console.log('[OCR] ÉTAPE 2 - Type MIME validé:', safeMime);

        let base64Data;
        let contentType;
        let dataUrlPreview;

        try {
            if (safeMime === 'application/pdf') {
                console.log('[OCR] ÉTAPE 3a - Traitement PDF');
                const pdfBuffer = fs.readFileSync(filePath);
                console.log('[OCR] PDF lu, taille:', pdfBuffer.length, 'bytes');

                // Conversion PDF en Base64
                base64Data = Buffer.from(pdfBuffer).toString('base64');
                contentType = 'application/pdf';
                console.log('[OCR] PDF converti en base64, longueur:', base64Data.length);
            } else {
                // Traitement d'image (optimisation avec sharp)
                console.log('[OCR] ÉTAPE 3b - Traitement Image');
                const optimized = await sharp(filePath)
                    .rotate()
                    .resize({ width: 1800, withoutEnlargement: true })
                    .jpeg({ quality: 80 })
                    .toBuffer();
                console.log('[OCR] Image optimisée, taille:', optimized.length, 'bytes');
                base64Data = optimized.toString('base64');
                contentType = 'image/jpeg';
                console.log('[OCR] Image convertie en base64, longueur:', base64Data.length);
            }

            dataUrlPreview = `data:${contentType};base64,...${base64Data.substring(base64Data.length - 10)}`;
            console.log('[OCR] ÉTAPE 3c - Data URL construite, aperçu:', dataUrlPreview);

            const dataSizeMB = base64Data.length * 0.75 / (1024 * 1024);
            console.log(`[OCR] ÉTAPE 3d - Taille du contenu à envoyer: ${dataSizeMB.toFixed(2)} MB`);
            if (dataSizeMB > 15) {
                console.warn('[OCR] AVERTISSEMENT: La taille du contenu est supérieure à 15MB, risque d\'échec API.');
            }

        } catch (error) {
            console.error('[OCR] ERREUR ÉTAPE 3 - Lecture/conversion fichier:', error);
            throw new Error('Impossible de lire le fichier: ' + error.message);
        }

        // Début de la construction de la requête API (Étapes 4 et 5 fusionnées)
        const prompt = this.buildExtractionPrompt();
        console.log('[OCR] ÉTAPE 4 - Prompt construit, longueur:', prompt.length);
        console.log('[OCR] ÉTAPE 5 - Envoi requête à OpenRouter');

        const fileContent = {};
        const dataUrl = `data:${contentType};base64,${base64Data}`;

        // Construction dynamique de l'objet pour le contenu multimédia (image_url ou file)
        if (contentType === 'application/pdf') {
            // Utilisation du type 'file' pour les PDFs
            fileContent.type = 'file';
            fileContent.file = {
                file_data: dataUrl,
                filename: path.basename(filePath)
            };
        } else {
            // Utilisation du type 'image_url' pour les images
            fileContent.type = 'image_url';
            fileContent.image_url = {
                url: dataUrl
            };
        }

        try {
            const response = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
                model: this.model,
                messages: [
                    {
                        role: 'user',
                        content: [
                            { type: 'text', text: prompt },
                            fileContent // L'objet construit (image_url ou file)
                        ]
                    }
                ],
                // Paramètres spécifiques pour l'extraction de données
                temperature: 0.1, 
                max_tokens: 4000 // Remis à 4000 (comme dans le 1er fragment) pour plus de flexibilité
            }, {
                headers: {
                    'Authorization': `Bearer ${this.apiKey}`,
                    'Content-Type': 'application/json',
                    // Utilisez une logique de fallback pour le referer
                    'HTTP-Referer': process.env.REFERER_URL || 'http://localhost:3000', 
                    'X-Title': 'e-dec Vehicles' // Maintenu le titre spécifique
                },
                timeout: 30000
            });

            console.log('[OCR] ÉTAPE 6 - Réponse reçue:', {
                status: response.status,
                hasChoices: !!response.data?.choices,
                choicesLength: response.data?.choices?.length
            });

            const extractedText = response.data.choices?.[0]?.message?.content || '';
            console.log('[OCR] ÉTAPE 7 - Contenu extrait (aperçu):', extractedText.substring(0, 200));

            // Parsing et validation
            const parsed = this.parseGeminiJSON(extractedText);
            console.log('[OCR] ÉTAPE 8 - JSON parsé et validé avec succès:', parsed);

            return parsed;

        } catch (error) {
            // Gestion détaillée des erreurs API (étapes 5/6)
            const status = error.response?.status || 'Aucun';
            const code = error.code || 'Inconnu';

            console.error('[OCR] ERREUR ÉTAPE 5/6 - Requête API:', {
                message: error.message,
                status: status,
                statusText: error.response?.statusText,
                data: error.response?.data
            });

            if (status === 401) {
                throw new Error('Clé API OpenRouter invalide ou expirée (401)');
            }
            if (status === 429) {
                throw new Error('Quota OpenRouter dépassé (429)');
            }
            if (code === 'ECONNABORTED') {
                throw new Error('Timeout: OpenRouter n\'a pas répondu dans les 30 secondes');
            }

            // Erreur générale
            throw new Error('Erreur API OpenRouter inattendue: ' + (error.response?.data?.error?.message || error.message));
        }
    }
}

// Exportation de l'instance du service pour une utilisation directe
module.exports = new OcrService();
----------------------------------------------------
server/services/edecGeneratorExport.js

class EdecGeneratorExport {
  generateExport(data) {
    const now = new Date().toISOString();
    const lang = (data.language || 'fr').toLowerCase();

    const xml = `<?xml version="1.0" encoding="UTF-8"?>
<EdecWeb version="4.0" createdDate="${now}">
  <goodsDeclarationType>
    <serviceType>2</serviceType>
    <declarationType>2</declarationType>
    <language>${lang}</language>
    <destinationCountry>${data.destination_country}</destinationCountry>

    <transportMeans>
      <transportMode>9</transportMode>
    </transportMeans>

    <exporter>
      <name>${data.user_name}</name>
      ${data.user_address ? `<street>${data.user_address}</street>` : ''}
      <postalCode>${data.user_zip || ''}</postalCode>
      <city>${data.user_city || ''}</city>
      <country>CH</country>
    </exporter>

    <goodsItem>
      <GoodsItemType>
        <description>${data.brand} ${data.model}${data.year ? ' ' + data.year : ''}</description>
        <commodityCode>${data.commodity_code || '8703.9060'}</commodityCode>
        <statisticalCode>${data.statistical_key || '911'}</statisticalCode>
        <additionalUnit>1</additionalUnit>

        <goodsItemDetails>
          <goodsItemDetail>
            <GoodsItemDetail>
              <name>2</name>
              <value>${data.vin}</value>
            </GoodsItemDetail>
          </goodsItemDetail>
        </goodsItemDetails>

        <packaging>
          <PackagingType>
            <packagingType>VN</packagingType>
            <quantity>1</quantity>
          </PackagingType>
        </packaging>
      </GoodsItemType>
    </goodsItem>
  </goodsDeclarationType>
</EdecWeb>`;
    return xml;
  }
}

module.exports = new EdecGeneratorExport();
----------------------------------------------------
server/services/exchangeRateService.js

const mysql = require('mysql2/promise');

class ExchangeRateService {
  constructor() {
    this.dbConfig = {
      host: process.env.DB_HOST,
      user: process.env.DB_USER,
      password: process.env.DB_PASS,
      database: process.env.DB_NAME
    };
  }

  async getCurrentRates() {
    const connection = await mysql.createConnection(this.dbConfig);
    try {
      const [rows] = await connection.execute(`
        SELECT currency_code, rate_to_chf, rate_date 
        FROM daily_exchange_rates 
        WHERE rate_date = (
          SELECT MAX(rate_date) FROM daily_exchange_rates
        )
        ORDER BY currency_code
      `);
      const rates = {};
      rows.forEach(r => rates[r.currency_code] = { rate: parseFloat(r.rate_to_chf), date: r.rate_date });
      return rates;
    } finally {
      await connection.end();
    }
  }

  async getRate(currency) {
    if (!currency) return 1.0;
    if (currency === 'CHF') return 1.0;
    const connection = await mysql.createConnection(this.dbConfig);
    try {
      const [rows] = await connection.execute(`
        SELECT rate_to_chf 
        FROM daily_exchange_rates 
        WHERE currency_code = ? 
        ORDER BY rate_date DESC 
        LIMIT 1
      `, [currency]);
      return rows.length > 0 ? parseFloat(rows[0].rate_to_chf) : 1.0;
    } finally {
      await connection.end();
    }
  }

  async convert(amount, fromCurrency, toCurrency = 'CHF') {
    const amt = Number(amount || 0);
    if (!amt) return 0;
    if (fromCurrency === toCurrency) return Math.round(amt * 100) / 100;
    const rateFrom = await this.getRate(fromCurrency);
    const rateTo = await this.getRate(toCurrency);
    const amountInCHF = amt * rateFrom;
    const converted = amountInCHF / rateTo;
    return Math.round(converted * 100) / 100;
  }
}

module.exports = new ExchangeRateService();
----------------------------------------------------
server/services/taresClassifier.js

const fs = require('fs');
const path = require('path');
// Assurez-vous que le chemin d'accès à votre pool de connexion DB est correct
const { pool } = require('../db'); 

/**
 * Gère la logique de classification des véhicules selon les règles TARES
 * et la validation/lookup des VIN.
 * * NOTE: Les règles TARES (taresRules) et la classification s'attendent à être passées
 * par le contrôleur ou lues à l'initialisation du serveur,
 * car la classe n'est plus chargée statiquement par le constructeur.
 */
class TaresClassifier {

    // Suppression du constructeur qui lisait statiquement les fichiers.

    normalizeFuelType(fuelType) {
        if (!fuelType) return 'essence';
        const normalized = fuelType.toLowerCase().trim();
        
        // La nouvelle logique de normalisation est plus simple et utilise des sous-chaînes
        if (normalized.includes('plug')) return 'hybride_plugin';
        if (normalized.includes('hybr')) return 'hybride';
        if (normalized.includes('elec') || normalized.includes('élec')) return 'electrique';
        if (normalized.includes('dies')) return 'diesel';
        if (normalized.includes('ess') || normalized.includes('petrol') || normalized.includes('gasoline')) return 'essence';
        
        return normalized;
    }

    matchesRule(fuel, cylinder, weightEmpty, rule) {
        if (rule.fuel && rule.fuel !== fuel) return false;
        
        if (rule.cylinder_min !== undefined && cylinder < rule.cylinder_min) return false;
        if (rule.cylinder_max !== undefined && cylinder > rule.cylinder_max) return false;
        
        // Utilisation de weightEmpty (poids à vide) pour la classification
        if (rule.weight_min !== undefined && weightEmpty < rule.weight_min) return false;
        if (rule.weight_max !== undefined && weightEmpty > rule.weight_max) return false;
        
        return true;
    }

    /**
     * Tente de classer le véhicule en utilisant les règles de configuration fournies.
     * Si aucune correspondance n'est trouvée, utilise un fallback détaillé.
     * @param {object} vehicleData - Données du véhicule (fuel_type, cc, weight, etc.)
     * @param {object} taresRules - Les règles TARES lues depuis le fichier de configuration (passées par l'appelant)
     */
    async classify(vehicleData, taresRules = {}) {
        const fuelType = this.normalizeFuelType(vehicleData.fuel_type || 'essence');
        const cc = parseInt(vehicleData.cylinder_capacity || 0, 10);
        const wEmpty = parseInt(vehicleData.weight_empty || 0, 10);
        const wTotal = parseInt(vehicleData.weight_total || 0, 10);

        // 1. Tente d'appliquer les règles de configuration d'abord
        for (const [code, rule] of Object.entries(taresRules || {})) {
            if (this.matchesRule(fuelType, cc, wEmpty, rule)) {
                const { statistical_keys = { '911': 'Automobile' }, description = 'Véhicule' } = rule;
                const { key, needs_user_selection, description: skDesc, available_keys } = this.selectStatisticalKey(statistical_keys, wTotal);
                
                return {
                    commodity_code: code,
                    description,
                    statistical_key: needs_user_selection ? null : key,
                    statistical_key_description: needs_user_selection ? null : (skDesc || statistical_keys[key]),
                    needs_user_selection,
                    available_keys: needs_user_selection ? statistical_keys : null
                };
            }
        }

        // 2. Logique de fallback manuelle pour une classification plus précise (comme les règles 8703.xx)
        if (fuelType === 'diesel') {
            if (cc <= 1500) return { commodity_code: '8703.3100', statistical_key: '911', description: 'Véhicule diesel ≤1500 cm3', needs_user_selection: false };
            if (cc > 1500 && cc <= 2500) {
                if (wEmpty <= 1200) return { commodity_code: '8703.3240', statistical_key: '911', description: 'Diesel >1500 ≤2500 cm3, ≤1200kg', needs_user_selection: false };
                if (wEmpty > 1200 && wEmpty <= 1600) return { commodity_code: '8703.3250', statistical_key: '911', description: 'Diesel >1500 ≤2500 cm3, >1200kg ≤1600kg', needs_user_selection: false };
                return { commodity_code: '8703.3260', statistical_key: '911', description: 'Diesel >1500 ≤2500 cm3, >1600kg', needs_user_selection: false };
            }
            if (cc > 2500) {
                if (wEmpty <= 1600) return { commodity_code: '8703.3330', statistical_key: '911', description: 'Diesel >2500 cm3, ≤1600kg', needs_user_selection: false };
                return { commodity_code: '8703.3340', statistical_key: '911', description: 'Diesel >2500 cm3, >1600kg', needs_user_selection: false };
            }
        }
        
        // 3. Fallback générique si rien ne correspond
        return {
            commodity_code: '8703.9060',
            statistical_key: '911',
            description: 'Autres véhicules',
            needs_user_selection: false
        };
    }

    selectStatisticalKey(keys, weightTotal) {
        if (!keys || Object.keys(keys).length === 0) return { key: '911', description: 'Automobile', needs_user_selection: false, available_keys: null };
        
        // Cas 1: Une seule clé disponible
        if (Object.keys(keys).length === 1) {
            const k = Object.keys(keys)[0];
            return { key: k, description: keys[k], needs_user_selection: false, available_keys: null };
        }
        
        // Cas 2: Sélection automatique basée sur le Poids Total (ex: 921 > 3500kg, 923 <= 3500kg)
        if (keys['921'] && keys['923']) {
            return weightTotal > 3500
                ? { key: '921', description: keys['921'], needs_user_selection: false, available_keys: null }
                : { key: '923', description: keys['923'], needs_user_selection: false, available_keys: null };
        }
        
        // Cas 3: Sélection manuelle requise
        return { key: null, description: null, needs_user_selection: true, available_keys: keys };
    }

    /**
     * Récupère le pays de fabrication (WMI) à partir du VIN en utilisant la base de données.
     * @param {string} vin - Numéro d'identification du véhicule.
     * @returns {Promise<string|null>} - Code pays (ex: 'FR') ou null.
     */
    async getCountryFromVIN_DB(vin) {
        if (!vin || vin.length < 2) return null;
        const wmi = vin.substring(0, 2).toUpperCase();
        
        // Utilisation du pool pour la connexion à la base de données
        const conn = await pool.getConnection();
        try {
            const [rows] = await conn.execute(`
                SELECT country_code FROM vin_wmi_country_codes
                WHERE ? BETWEEN wmi_start AND wmi_end
                LIMIT 1
            `, [wmi]);
            
            // Assurez-vous d'avoir des colonnes 'wmi_start', 'wmi_end' et 'country_code' dans votre table SQL.
            return rows?.[0]?.country_code || null;
        } catch (error) {
            console.error("Erreur DB WMI:", error.message);
            return null;
        } finally {
            if (conn) conn.release();
        }
    }
}

// Exportation en tant qu'instance unique (Singleton)
module.exports = new TaresClassifier();
----------------------------------------------------
server/db.js

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASS,
  database: process.env.DB_NAME,
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

module.exports = {
  pool,
  getConnection: () => pool.getConnection()
};
----------------------------------------------------
server/controllers/homeController.js

// Obsolète (SPA), conservé pour compatibilité si utilisé ailleurs
const path = require('path');
const router = require('express').Router();

router.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
});

router.get('/:lang', (req, res) => {
  res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
});

module.exports = router;
----------------------------------------------------
server/controllers/importController.js

// Obsolète, les routes actuelles sont dans /server/routes/api.js
const router = require('express').Router();

router.get('/ping', (req, res) => res.json({ ok: true }));

module.exports = router;
----------------------------------------------------
server/controllers/exportController.js

// Obsolète, les routes actuelles sont dans /server/routes/api.js
module.exports = {
  submit: (req, res) => {
    const xml = `<?xml version="1.0" encoding="UTF-8"?>
<EdecWeb version="4.0">
  <goodsDeclarationType>
    <serviceType>2</serviceType>
  </goodsDeclarationType>
</EdecWeb>`;
    res.set('Content-Type', 'application/xml');
    res.set('Content-Disposition', `attachment; filename="edec-export-${Date.now()}.xml"`);
    res.send(xml);
  }
};
----------------------------------------------------
public/js/controllers/homeController.js

angular.module('edecApp')
  .controller('HomeController', function($scope) {
    $scope.features = [
      { icon: '⚡', title: 'Rapide', description: 'Générez votre déclaration en moins de 5 minutes' },
      { icon: '💰', title: 'Économique', description: 'Tarifs transparents, outil personnel' },
      { icon: '✅', title: 'Simple', description: 'Interface intuitive, étapes guidées' },
      { icon: '🤖', title: 'IA intégrée', description: 'Extraction automatique depuis votre carte grise' }
    ];
  });
----------------------------------------------------
public/js/controllers/importController.js

angular.module('edecApp')
  .controller('ImportController', function($scope, $http, $window, $rootScope) {
    // Initialisation des variables
    $scope.step = 1;
    $scope.method = null;
    $scope.vehicle = {};
    $scope.parties = {
      importer: { name: '', firstname: '', address: '', zip: '', city: '', ide: '' },
      consignee: { name: '', firstname: '', address: '', zip: '', city: '', ide: '' }, // Ajout de firstname pour les parties
      declarant: { name: '', firstname: '', address: '', zip: '', city: '', ide: '' } // Ajout de firstname pour les parties
    };
    $scope.business = { cash_payment: false };
    $scope.declaration = {
      transport_mode: '9',
      purchase_currency: 'EUR',
      purchase_price: 0,
      transport_cost: 0,
      other_costs: 0,
      language: $rootScope.lang || 'fr',
      dispatch_country: ''
    };
    $scope.countries = {};
    $scope.currencies = {};
    $scope.classification = null;
    $scope.ocrLoading = false;
    $scope.generating = false;
    // [NOUVEAU] Drapeau pour indiquer si une tentative d'OCR a eu lieu
    $scope.ocrAttempted = false;
    $scope.statisticalValueCHF = 0;
    $scope.vatValueCHF = 0;
    $scope.vinValid = null;

    // Nouvelle variable pour gérer l'état du champ prénom dans la vue (ng-disabled)
    $scope.isCompany = false;

    // Load configs
    $http.get('/api/countries').then(function(res) {
      $scope.countries = res.data;
    });
    $http.get('/api/currencies').then(function(res) {
      $scope.currencies = res.data;
    });

    // NOUVELLE LOGIQUE: Surveiller l'IDE pour déterminer si c'est une société et griser le prénom
    $scope.$watch('parties.importer.ide', function(newVal) {
      // Si l'IDE est rempli (et différent de la valeur par défaut pour particulier, si applicable), on suppose que c'est une société.
      $scope.isCompany = !!newVal && newVal.trim() !== 'CHE222251936';
      if ($scope.isCompany) {
        // Optionnel : vider le prénom si on passe en mode société, mais l'utilisateur a demandé de ne pas vider, donc on laisse le contenu.
      }
    });

    $scope.selectMethod = function(method) {
      $scope.method = method;
      $scope.step = 2;
    };

    $scope.triggerFileInput = function() {
      document.getElementById('file-upload').click();
    };

    // OCR upload
    $scope.handleFileSelect = function(event) {
      const files = event.target.files;
      if (!files || files.length === 0) {
        return;
      }

      $scope.$apply(function() {
        $scope.ocrLoading = true;
        // [MODIFICATION] On marque la tentative au début de la fonction
        $scope.ocrAttempted = true;
      });

      const formData = new FormData();
      formData.append('registration_card', files[0]);

      $http.post('/api/ocr', formData, {
        transformRequest: angular.identity,
        headers: { 'Content-Type': undefined }
      })
      .then(function(response) {
        Object.assign($scope.vehicle, response.data);
        $scope.ocrLoading = false;

        // [MODIFICATION] Si l'OCR réussit, on peut considérer qu'il n'y a pas d'échec d'affichage du message d'erreur
        // Bien que 'ocrAttempted' soit déjà true, on ne le remet pas à false pour garder l'info de la tentative.

        if ($scope.vehicle.vin) {
          $scope.validateVIN();
        }
        $scope.updateClassification();
      })
      .catch(function(error) {
        const details = error.data?.details || error.data?.error || 'Erreur inconnue';
        const hint = error.data?.hint || '';
        alert('Erreur OCR: ' + details + (hint ? '\n\n' + hint : ''));
        console.error('Détails complets:', error.data);
        $scope.ocrLoading = false;

        // [NOTE]: L'erreur sera affichée dans la vue car ocrAttempted est true 
        // et $scope.vehicle est resté vide ou a été vidé lors de l'échec.
      });
    };

    $scope.validateVIN = function() {
      if (!$scope.vehicle.vin || $scope.vehicle.vin.length !== 17) {
        $scope.vinValid = false;
        return;
      }
      $http.post('/api/validate-vin', { vin: $scope.vehicle.vin })
        .then(function(response) {
          $scope.vinValid = response.data.valid;
          if (response.data.valid && response.data.country) {
            $scope.declaration.dispatch_country = response.data.country;
          }
        });
    };

    $scope.updateClassification = function() {
      if (!$scope.vehicle.fuel_type || !$scope.vehicle.weight_empty) {
        return;
      }
      $http.post('/api/classify-vehicle', $scope.vehicle)
        .then(function(response) {
          $scope.classification = response.data;
          $scope.vehicle.commodity_code = response.data.commodity_code;
          if (!response.data.needs_user_selection) {
            $scope.vehicle.statistical_key = response.data.statistical_key;
          }
        });
    };

    // CORRECTION: Assure que les valeurs CHF sont calculées et arrondies à l'entier
    $scope.calculateTotal = function() {
      const price = parseFloat($scope.declaration.purchase_price) || 0;
      const transport = parseFloat($scope.declaration.transport_cost) || 0;
      const other = parseFloat($scope.declaration.other_costs) || 0;
      const currency = $scope.declaration.purchase_currency;

      // 1. Valeur Statistique (Statistical Value) = Prix d'achat seul
      $http.post('/api/convert-currency', {
        amount: price,
        from: currency,
        to: 'CHF'
      })
      .then(function(response) {
        // Conversion en entier obligatoire
        $scope.statisticalValueCHF = Math.round(response.data.converted);

        // 2. Base TVA (VAT Value) = Prix d'achat + Transport + Autres frais
        return $http.post('/api/convert-currency', {
          amount: price + transport + other,
          from: currency,
          to: 'CHF'
        });
      })
      .then(function(responseVat) {
        // Conversion en entier obligatoire
        $scope.vatValueCHF = Math.round(responseVat.data.converted);
      });
    };

    $scope.nextStep = function() {
      if ($scope.step === 2) {
        if (!$scope.vehicle.vin || !$scope.vehicle.brand || !$scope.vehicle.model || !$scope.vehicle.weight_empty || !$scope.vehicle.weight_total) {
          alert('Veuillez remplir les informations du véhicule');
          return;
        }
      }
      $scope.step++;
    };

    $scope.prevStep = function() {
      $scope.step--;
    };

    $scope.transportModes = [
      { value: '9', label: 'Autopropulsion' },
      { value: '7', label: 'Pipeline, etc.' },
      { value: '4', label: 'Trafic aérien' },
      { value: '2', label: 'Trafic ferroviaire' },
      { value: '8', label: 'Trafic par eau' },
      { value: '3', label: 'Trafic routier' }
    ];

    // FONCTION UTILITAIRE: Décide si on concatène Nom et Prénom ou si on prend juste le Nom de la société.
    const getFullName = function(name, firstname, ide) {
      // Vérifie si l'IDE est rempli et n'est pas l'IDE par défaut pour particulier
      const isCompany = !!ide && ide.trim() !== 'CHE222251936';

      if (isCompany) {
        return name || ''; // Retourne la raison sociale
      }
      // Pour les particuliers, on concatène (Prénom Nom)
      const parts = [firstname, name].filter(function(p) { return p && p.trim(); });
      return parts.join(' ');
    };

    $scope.generateDeclaration = function() {
      if ($scope.generating) {
        return;
      }
      const imp = $scope.parties.importer;
      if (!imp.name || !imp.zip || !imp.city) {
        alert('Veuillez renseigner les infos Importateur');
        return;
      }
      if (!$scope.declaration.dispatch_country) {
        alert('Veuillez sélectionner le pays d\'expédition');
        return;
      }

      $scope.generating = true;

      // Détermination du nom formaté pour l'Importateur/User
      const importerFullName = getFullName(imp.name, imp.firstname, imp.ide);

      // Détermination du nom pour le Consignataire (prend ceux de l'Importateur si vides)
      const cons = $scope.parties.consignee;
      const consigneeIsImporter = !cons.name && !cons.ide;
      const consigneeName = consigneeIsImporter
        ? importerFullName
        : getFullName(cons.name, cons.firstname, cons.ide);
      const consigneeIde = cons.ide || imp.ide;

      // Détermination du nom pour le Déclarant (prend ceux de l'Importateur si vides)
      const decl = $scope.parties.declarant;
      const declarantName = getFullName(
        decl.name || imp.name,
        decl.firstname || imp.firstname,
        decl.ide || imp.ide
      );

      const payload = Object.assign({}, $scope.vehicle, $scope.declaration, {
        user_name: importerFullName,
        user_address: imp.address,
        user_zip: imp.zip,
        user_city: imp.city,
        user_ide: imp.ide,

        consignee_name: consigneeName,
        consignee_address: cons.address || imp.address,
        consignee_zip: cons.zip || imp.zip,
        consignee_city: cons.city || imp.city,
        consignee_ide: consigneeIde,

        declarant_name: declarantName,
        declarant_address: decl.address || imp.address,
        declarant_zip: decl.zip || imp.zip,
        declarant_city: decl.city || imp.city,
        declarant_ide: decl.ide || '',

        cash_payment: $scope.business.cash_payment ? 1 : 0,

        statistical_value_chf: $scope.statisticalValueCHF,
        vat_value_chf: $scope.vatValueCHF,

        language: $rootScope.lang
      });

      $http.post('/api/generate-import', payload, { responseType: 'blob' })
        .then(function(response) {
          const blob = new Blob([response.data], { type: 'application/xml' });
          const url = $window.URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.href = url;
          a.download = 'edec-import-' + Date.now() + '.xml';
          document.body.appendChild(a);
          a.click();
          document.body.removeChild(a);
          $window.URL.revokeObjectURL(url);
          
          // Récupérer le sessionToken depuis les headers de réponse
          const sessionToken = response.headers('X-Session-Token');
          
          if (sessionToken && confirm('Voulez-vous soumettre automatiquement cette déclaration au portail e-dec?')) {
            $scope.submitToEdec(sessionToken);
          } else {
            $scope.step = 4;
            $scope.generating = false;
          }
        })
        .catch(function(error) {
          alert('Erreur: ' + (error.data?.error || 'Erreur de génération'));
          $scope.generating = false;
        });
    };

    $scope.submitToEdec = function(sessionToken) {
      $http.post('/api/submit-to-edec', { 
        sessionToken: sessionToken,
        xmlPath: '/path/to/xml/' + sessionToken + '.xml' // Le backend construira le bon chemin
      })
      .then(function(response) {
        alert('✅ Déclaration soumise avec succès!\nNuméro: ' + response.data.declarationNumber);
        $scope.step = 4;
        $scope.generating = false;
      })
      .catch(function(error) {
        alert('⚠️ Soumission automatique échouée: ' + (error.data?.details || error.data?.error));
        $scope.step = 4;
        $scope.generating = false;
      });
    };

    $scope.resetForm = function() {
      $scope.step = 1;
      $scope.method = null;
      $scope.vehicle = {};
      $scope.parties = {
        importer: { name: '', firstname: '', address: '', zip: '', city: '', ide: '' },
        consignee: { name: '', firstname: '', address: '', zip: '', city: '', ide: '' },
        declarant: { name: '', firstname: '', address: '', zip: '', city: '', ide: '' }
      };
      $scope.business = { cash_payment: false };
      $scope.declaration = {
        transport_mode: '9',
        purchase_currency: 'EUR',
        purchase_price: 0,
        transport_cost: 0,
        other_costs: 0,
        language: $rootScope.lang || 'fr',
        dispatch_country: ''
      };
      $scope.classification = null;
      $scope.statisticalValueCHF = 0;
      $scope.vatValueCHF = 0;
      $scope.vinValid = null;
      $scope.isCompany = false;
      // [MODIFICATION] Réinitialisation de l'état
      $scope.ocrAttempted = false;
    };
  });
----------------------------------------------------
public/js/controllers/exportController.js

angular.module('edecApp')
  .controller('ExportController', function($scope, $http, $window, $rootScope) {
    $scope.vehicle = {};
    $scope.export = { export_type: 'temporary', language: $rootScope.lang || 'fr' };
    $scope.parties = { exporter: { name: '', address: '', zip: '', city: '', ide: '' } };
    $scope.countries = {};
    $scope.generating = false;

    $http.get('/api/countries').then(res => $scope.countries = res.data);

    $scope.generateExport = function() {
      if ($scope.generating) return;
      const ex = $scope.parties.exporter;
      if (!ex.name || !ex.zip || !ex.city) { alert('Veuillez renseigner les infos Exportateur'); return; }
      if (!$scope.export.destination_country) { alert('Veuillez sélectionner le pays de destination'); return; }

      $scope.generating = true;
      const data = Object.assign({}, $scope.vehicle, $scope.export, {
        user_name: ex.name,
        user_address: ex.address,
        user_zip: ex.zip,
        user_city: ex.city,
        user_ide: ex.ide
      });

      $http.post('/api/generate-export', data, { responseType: 'blob' })
        .then(function(response) {
          const blob = new Blob([response.data], { type: 'application/xml' });
          const url = $window.URL.createObjectURL(blob);
          const a = document.createElement('a');
          a.href = url;
          a.download = 'edec-export-' + Date.now() + '.xml';
          document.body.appendChild(a);
          a.click();
          document.body.removeChild(a);
          $window.URL.revokeObjectURL(url);
          alert('✅ Déclaration d\'export générée avec succès !');
          $scope.generating = false;
        }).catch(function(error) {
          alert('Erreur: ' + (error.data?.error || 'Erreur de génération'));
          $scope.generating = false;
        });
    };
  });
----------------------------------------------------
public/js/app.js

angular.module('edecApp', ['ngRoute'])
  .config(['$routeProvider', '$locationProvider', function($routeProvider, $locationProvider) {
    $locationProvider.html5Mode({ enabled: true, requireBase: true });

    $routeProvider
      .when('/', { templateUrl: 'views/home.html', controller: 'HomeController' })
      .when('/import', { templateUrl: 'views/import.html', controller: 'ImportController' })
      .when('/export', { templateUrl: 'views/export.html', controller: 'ExportController' })
      .otherwise({ redirectTo: '/' });
  }])
  .controller('MainController', ['$scope', '$location', function($scope, $location) {
    $scope.currentPath = function() { return $location.path(); };
  }])
  .run(['$rootScope', function($rootScope) {
    // Language handling
    const SUPPORTED = ['fr', 'de', 'it', 'en'];
    const stored = localStorage.getItem('edec_lang');
    const defaultLang = 'fr';
    $rootScope.lang = SUPPORTED.includes(stored) ? stored : defaultLang;
    $rootScope.setLang = function(l) {
      if (SUPPORTED.includes(l)) {
        $rootScope.lang = l;
        localStorage.setItem('edec_lang', l);
      }
    };
    $rootScope.isLang = function(l) { return $rootScope.lang === l; };

    // Basic dictionary (minimal; page reste FR par défaut mais on met à jour XML/langue)
    $rootScope.t = function(s) { return s; };

    $rootScope.apiUrl = '/api';
  }]);
----------------------------------------------------
public/index.html

<!DOCTYPE html>
<html lang="fr" ng-app="edecApp">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>e-dec Véhicules - Déclaration Simplifiée</title>
  <base href="/">
  <link rel="stylesheet" href="/css/style.css">
  
  <!-- AngularJS CDN -->
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.3/angular-route.min.js"></script>
</head>
<body ng-controller="MainController">
  <!-- Navigation -->
  <nav class="navbar">
    <div class="container">
      <a href="/" class="logo">🚗 e-dec Véhicules</a>
      <ul class="nav-menu">
        <a href="/import" ng-class="{active: currentPath() == '/import'}">Importer</a>
        <a href="/export" ng-class="{active: currentPath() == '/export'}">Exporter</a>
        <a href="/" ng-class="{active: currentPath() == '/'}">Accueil</a>
      </ul>
      <div class="lang-switcher">
        <button class="btn-lang" ng-class="{active: isLang('fr')}" ng-click="setLang('fr')">FR</button>
        <button class="btn-lang" ng-class="{active: isLang('de')}" ng-click="setLang('de')">DE</button>
        <button class="btn-lang" ng-class="{active: isLang('it')}" ng-click="setLang('it')">IT</button>
        <button class="btn-lang" ng-class="{active: isLang('en')}" ng-click="setLang('en')">EN</button>
      </div>
    </div>
  </nav>
  
  <!-- Contenu principal -->
  <main class="main-content" ng-view></main>
  
  <!-- Footer -->
  <footer class="footer">
    <div class="container">
      <p>&copy; 2025 e-dec Véhicules. Simplifiez vos déclarations douanières.</p>
      <p class="disclaimer">⚠️ Cet outil aide à créer votre déclaration. La responsabilité des informations incombe au déclarant.</p>
    </div>
  </footer>
  
  <!-- Scripts AngularJS -->
    <script src="/js/app.js"></script>
    <script src="/js/controllers/homeController.js"></script>
    <script src="/js/controllers/importController.js"></script>
    <script src="/js/controllers/exportController.js"></script>

</body>
</html>
----------------------------------------------------
public/css/style.css

/* Reset & Base */
* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
    line-height: 1.6;
    color: #333;
    background: #f5f7fa;
}

.container {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 20px;
}

/* Navigation */
.navbar {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    padding: 1rem 0;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.navbar .container {
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
}

.logo {
    font-size: 1.5rem;
    font-weight: bold;
    color: white;
    text-decoration: none;
}

.nav-menu {
    display: flex;
    list-style: none;
    gap: 2rem;
}

.nav-menu a {
    color: rgba(255,255,255,0.9);
    text-decoration: none;
    font-weight: 500;
    transition: color 0.3s;
}

.nav-menu a:hover,
.nav-menu a.active {
    color: white;
}

.lang-switcher {
    display: flex;
    gap: 0.5rem;
}

.btn-lang {
    padding: 0.5rem 1rem;
    border: 1px solid rgba(255,255,255,0.3);
    background: rgba(255,255,255,0.1);
    color: white;
    cursor: pointer;
    border-radius: 5px;
    transition: all 0.3s;
}

.btn-lang:hover,
.btn-lang.active {
    background: rgba(255,255,255,0.2);
    border-color: white;
}

/* Hero */
.hero {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    padding: 5rem 0;
    text-align: center;
}

.hero h1 {
    font-size: 3rem;
    margin-bottom: 1rem;
}

.subtitle {
    font-size: 1.25rem;
    margin-bottom: 2rem;
    opacity: 0.9;
}

.cta-buttons {
    display: flex;
    gap: 1rem;
    justify-content: center;
    flex-wrap: wrap;
}

/* Buttons */
.btn {
    padding: 0.875rem 2rem;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: all 0.3s;
    display: inline-block;
}

.btn-primary {
    background: #667eea;
    color: white;
}

.btn-primary:hover {
    background: #5568d3;
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(102,126,234,0.4);
}

.btn-secondary {
    background: #6c757d;
    color: white;
}

.btn-secondary:hover {
    background: #5a6268;
}

/* Features */
.features {
    padding: 5rem 0;
    background: white;
}

.features h2 {
    text-align: center;
    margin-bottom: 3rem;
    font-size: 2.5rem;
    color: #333;
}

.features-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 2rem;
}

.feature-card {
    text-align: center;
    padding: 2rem;
    border-radius: 12px;
    background: #f8f9fa;
    transition: transform 0.3s;
}

.feature-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 8px 20px rgba(0,0,0,0.1);
}

.feature-icon {
    font-size: 3rem;
    margin-bottom: 1rem;
}

/* How it works */
.how-it-works {
    padding: 5rem 0;
}

.how-it-works h2 {
    text-align: center;
    margin-bottom: 3rem;
    font-size: 2.5rem;
}

.steps {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 2rem;
}

.step-item {
    text-align: center;
}

.step-number {
    width: 60px;
    height: 60px;
    background: #667eea;
    color: white;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.5rem;
    font-weight: bold;
    margin: 0 auto 1rem;
}

/* Progress Bar */
.progress-bar {
    display: flex;
    justify-content: space-between;
    margin: 2rem 0 3rem;
    position: relative;
}

.progress-bar::before {
    content: '';
    position: absolute;
    top: 20px;
    left: 0;
    right: 0;
    height: 2px;
    background: #ddd;
    z-index: 0;
}

.progress-step {
    display: flex;
    flex-direction: column;
    align-items: center;
    position: relative;
    z-index: 1;
}

.step-num {
    width: 40px;
    height: 40px;
    background: #ddd;
    color: #666;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
    margin-bottom: 0.5rem;
}

.progress-step.active .step-num {
    background: #667eea;
    color: white;
}

.progress-step.completed .step-num {
    background: #28a745;
    color: white;
}

/* Method Cards */
.method-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 2rem;
    margin: 2rem 0;
}

.method-card {
    text-align: center;
    padding: 3rem 2rem;
    border: 2px solid #ddd;
    border-radius: 12px;
    cursor: pointer;
    transition: all 0.3s;
}

.method-card:hover {
    border-color: #667eea;
    transform: translateY(-5px);
    box-shadow: 0 8px 20px rgba(102,126,234,0.2);
}

.method-icon {
    font-size: 4rem;
    margin-bottom: 1rem;
}

/* Form */
.form {
    max-width: 800px;
    margin: 2rem auto;
    background: white;
    padding: 2rem;
    border-radius: 12px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

fieldset {
    border: 1px solid #ddd;
    padding: 1.5rem;
    margin-bottom: 2rem;
    border-radius: 8px;
}

legend {
    font-weight: bold;
    padding: 0 0.5rem;
    color: #667eea;
}

.form-group {
    margin-bottom: 1.5rem;
}

.form-group label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: 500;
    color: #333;
}

.form-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 1rem;
}

/* Style des inputs: inclut text, email, number et select */
input[type="text"],
input[type="email"],
input[type="number"],
select {
    width: 100%;
    padding: 0.75rem;
    border: 1px solid #ddd;
    border-radius: 6px;
    font-size: 1rem;
    transition: border-color 0.3s;
}

input:focus,
select:focus {
    outline: none;
    border-color: #667eea;
    box-shadow: 0 0 0 3px rgba(102,126,234,0.1);
}

.hint {
    display: block;
    font-size: 0.875rem;
    color: #28a745;
    margin-top: 0.25rem;
}

.error {
    display: block;
    font-size: 0.875rem;
    color: #dc3545;
    margin-top: 0.25rem;
}

/* Upload Box */
.upload-box {
    border: 3px dashed #ddd;
    border-radius: 12px;
    padding: 3rem;
    text-align: center;
    margin: 2rem 0;
    cursor: pointer;
    transition: all 0.3s;
}

.upload-box:hover {
    border-color: #667eea;
    background: #f8f9fa;
}

.upload-label {
    font-size: 1.25rem;
    cursor: pointer;
}

/* Classification Info */
.classification-info {
    background: #e7f3ff;
    border-left: 4px solid #667eea;
    padding: 1.5rem;
    margin: 1.5rem 0;
    border-radius: 6px;
}

/* Total Box */
.total-box {
    background: #d4edda;
    border: 2px solid #28a745;
    padding: 1.5rem;
    border-radius: 8px;
    margin: 1.5rem 0;
    text-align: center;
}

.total-box h3 {
    color: #155724;
    margin: 0;
}

/* Checkbox */
.checkbox-label {
    display: flex;
    align-items: center;
    cursor: pointer;
    margin: 1rem 0;
}

.checkbox-label input[type="checkbox"] {
    width: auto;
    margin-right: 0.75rem; /* Mieux */
    height: 1.1em;         /* Mieux */
    width: 1.1em;          /* Mieux */
}

/* Form Actions */
.form-actions {
    display: flex;
    gap: 1rem;
    justify-content: space-between; /* Mieux pour la navigation Précédent/Suivant */
    margin-top: 2rem;
}

/* Success */
.success-content {
    text-align: center;
    padding: 3rem 0;
}

.success-icon {
    font-size: 5rem;
    margin-bottom: 1rem;
    color: #28a745; /* Mieux: couleur de succès */
}

.next-steps-box {
    background: white;
    border: 1px solid #ddd;
    border-radius: 12px;
    padding: 2rem;
    margin: 2rem auto;
    max-width: 600px;
    text-align: left;
}

.next-steps-box ol {
    padding-left: 1.5rem; /* Mieux que margin-left */
}

.next-steps-box li {
    margin-bottom: 0.5rem;
}

/* Footer */
.footer {
    background: #2c3e50;
    color: white;
    padding: 2rem 0;
    text-align: center;
    margin-top: 3rem;
}

.disclaimer {
    font-size: 0.875rem;
    opacity: 0.8;
    margin-top: 0.5rem;
}

/* Main Content */
.main-content {
    min-height: calc(100vh - 300px);
    padding: 2rem 0;
}

/* Responsive */
@media (max-width: 768px) {
    .hero h1 {
        font-size: 2rem;
    }
    
    .navbar .container {
        flex-direction: column;
        gap: 1rem;
    }
    
    .nav-menu {
        flex-direction: column;
        gap: 0.5rem;
        text-align: center;
        width: 100%;
    }
    
    .form-row {
        grid-template-columns: 1fr;
    }
    
    .form-actions {
        flex-direction: column-reverse; /* Mieux: met le bouton principal en bas */
    }
    
    .btn {
        width: 100%;
    }
}
----------------------------------------------------
public/views/home.html

<div class="hero">
    <div class="container">
        <h1>🚗 Déclaration e-dec Simplifiée</h1>
        <p class="subtitle">Importez ou exportez votre véhicule en Suisse en quelques clics</p>
        <div class="cta-buttons">
            <a href="/import" class="btn btn-primary">Importer un véhicule</a>
            <a href="/export" class="btn btn-secondary">Exporter un véhicule</a>
        </div>
    </div>
</div>

<section class="features">
    <div class="container">
        <h2>Pourquoi choisir notre service ?</h2>
        <div class="features-grid">
            <div class="feature-card" ng-repeat="feature in features">
                <div class="feature-icon">{{ feature.icon }}</div>
                <h3>{{ feature.title }}</h3>
                <p>{{ feature.description }}</p>
            </div>
        </div>
    </div>
</section>

<section class="how-it-works">
    <div class="container">
        <h2>Comment ça marche ?</h2>
        <div class="steps">
            <div class="step-item">
                <div class="step-number">1</div>
                <h3>Choisissez votre méthode</h3>
                <p>Scanner la carte grise ou saisie manuelle</p>
            </div>
            <div class="step-item">
                <div class="step-number">2</div>
                <h3>Remplissez les informations</h3>
                <p>Véhicule, finances et coordonnées</p>
            </div>
            <div class="step-item">
                <div class="step-number">3</div>
                <h3>Générez le fichier XML</h3>
                <p>Téléchargez votre déclaration e-dec prête à l'emploi</p>
            </div>
            <div class="step-item">
                <div class="step-number">4</div>
                <h3>Soumettez à la douane</h3>
                <p>Importez le fichier sur le portail officiel e-dec</p>
            </div>
        </div>
    </div>
</section>
----------------------------------------------------
public/views/export.html

<div class="container">
    <h1>Exporter un Véhicule depuis la Suisse</h1>
    <p>Cette page vous permet de générer une déclaration d'exportation pour un véhicule sortant du territoire suisse.</p>
    
    <form name="exportForm" class="form" ng-submit="generateExport()" novalidate>
        <fieldset>
            <legend>Informations Exportateur</legend>
            <div class="form-row">
                <div class="form-group">
                    <label>Nom ou raison sociale *</label>
                    <input type="text" ng-model="parties.exporter.name" required>
                </div>
                 <div class="form-group">
                    <label>Numéro IDE</label>
                    <input type="text" ng-model="parties.exporter.ide" placeholder="Optionnel">
                </div>
            </div>
            <div class="form-group">
                <label>Adresse</label>
                <input type="text" ng-model="parties.exporter.address">
            </div>
            <div class="form-row">
                <div class="form-group">
                    <label>NPA *</label>
                    <input type="text" ng-model="parties.exporter.zip" required>
                </div>
                <div class="form-group">
                    <label>Ville *</label>
                    <input type="text" ng-model="parties.exporter.city" required>
                </div>
            </div>
        </fieldset>

        <fieldset>
            <legend>Informations Véhicule</legend>
            <div class="form-group">
                <label>VIN (Numéro de châssis) *</label>
                <input type="text" ng-model="vehicle.vin" maxlength="17" minlength="17" required style="text-transform:uppercase">
            </div>
            
            <div class="form-row">
                <div class="form-group">
                    <label>Marque *</label>
                    <input type="text" ng-model="vehicle.brand" required>
                </div>
                <div class="form-group">
                    <label>Modèle *</label>
                    <input type="text" ng-model="vehicle.model" required>
                </div>
                 <div class="form-group">
                    <label>Année (optionnel)</label>
                    <input type="number" ng-model="vehicle.year">
                </div>
            </div>
        </fieldset>
        
        <fieldset>
            <legend>Informations d'Exportation</legend>
            <div class="form-group">
                <label>Pays de destination *</label>
                <select ng-model="export.destination_country" required 
                        ng-options="code as name for (code, name) in countries">
                    <option value="">Sélectionner un pays...</option>
                </select>
            </div>
            
            <div class="form-group">
                <label>Type d'exportation (informatif)</label>
                <select ng-model="export.export_type">
                    <option value="temporary">Temporaire (retour prévu)</option>
                    <option value="definitive">Définitive</option>
                </select>
            </div>
        </fieldset>
        
        <div class="form-actions" style="justify-content: flex-end;">
            <button type="submit" class="btn btn-primary" ng-disabled="exportForm.$invalid || generating">
                <span ng-if="!generating">Générer la déclaration ✨</span>
                <span ng-if="generating">Génération en cours...</span>
            </button>
        </div>
    </form>
</div>
----------------------------------------------------
public/views/import.html

<div class="container">
    <h1>Importer un Véhicule en Suisse</h1>
    
    <div class="progress-bar" ng-show="step > 1">
        <div class="progress-step" ng-class="{active: step >= 1, completed: step > 1}">
            <span class="step-num">1</span>
            <span class="step-label">Méthode</span>
        </div>
        <div class="progress-step" ng-class="{active: step >= 2, completed: step > 2}">
            <span class="step-num">2</span>
            <span class="step-label">Véhicule</span>
        </div>
        <div class="progress-step" ng-class="{active: step >= 3, completed: step > 3}">
            <span class="step-num">3</span>
            <span class="step-label">Déclaration</span>
        </div>
        <div class="progress-step" ng-class="{active: step >= 4}">
            <span class="step-num">4</span>
            <span class="step-label">Résultat</span>
        </div>
    </div>
    
    <div ng-show="step === 1" class="step-content">
        <h2 style="text-align: center; margin-bottom: 2rem;">Choisissez votre méthode de saisie</h2>
        <div class="method-cards">
            <div class="method-card" ng-click="selectMethod('ocr')">
                <div class="method-icon">📸</div>
                <h3>Scanner la carte grise</h3>
                <p>Extraction automatique des données par IA (JPG, PNG, PDF)</p>
            </div>
            <div class="method-card" ng-click="selectMethod('manual')">
                <div class="method-icon">⌨️</div>
                <h3>Saisie manuelle</h3>
                <p>Remplir le formulaire pas à pas</p>
            </div>
        </div>
    </div>
    
    <div ng-show="step === 2" class="step-content">
        <h2 style="text-align: center;">Informations du véhicule</h2>
        
        <div ng-if="method === 'ocr'" class="ocr-section">
            <div class="upload-box" ng-click="triggerFileInput()">
                <label class="upload-label">
                    <span ng-if="!ocrLoading">📷 Photographier ou choisir l'image/PDF</span>
                    <span ng-if="ocrLoading">⏳ Extraction en cours...</span>
                </label>
                <input id="file-upload" type="file" accept="image/*,application/pdf" 
                        style="display:none"
                        onchange="angular.element(this).scope().handleFileSelect(event)">
            </div>
        </div>
        
        <form name="vehicleForm" class="form" ng-show="method === 'manual' || vehicle.vin || vehicle.brand" novalidate>
            <div class="form-group">
                <label>VIN (Numéro de châssis / 17 caractères) *</label>
                <input type="text" ng-model="vehicle.vin" maxlength="17" minlength="17"
                         ng-change="validateVIN()" required style="text-transform:uppercase">
                <span class="hint" ng-if="vinValid">✅ VIN valide. Pays d'origine détecté: {{declaration.dispatch_country || '...'}}</span>
                <span class="error" ng-if="vinValid === false">❌ Le format du VIN est invalide (17 caractères alphanumériques, sans I, O, Q).</span>
            </div>
            
            <div class="form-row">
                <div class="form-group">
                    <label>Marque *</label>
                    <input type="text" ng-model="vehicle.brand" required>
                </div>
                <div class="form-group">
                    <label>Modèle *</label>
                    <input type="text" ng-model="vehicle.model" required>
                </div>
            </div>
            
            <div class="form-row">
                <div class="form-group">
                    <label>Année de 1ère mise en circulation</label>
                    <input type="number" ng-model="vehicle.year" min="1900" max="2026">
                </div>
                <div class="form-group">
                    <label>Type de carburant *</label>
                    <select ng-model="vehicle.fuel_type" ng-change="updateClassification()" required>
                        <option value="">Sélectionner...</option>
                        <option value="essence">Essence</option>
                        <option value="diesel">Diesel</option>
                        <option value="electrique">Électrique</option>
                        <option value="hybride">Hybride</option>
                        <option value="hybride_plugin">Hybride rechargeable</option>
                    </select>
                </div>
            </div>
            
            <div class="form-row">
                <div class="form-group">
                    <label>Cylindrée (cm³)</label>
                    <input type="number" ng-model="vehicle.cylinder_capacity" ng-change="updateClassification()">
                </div>
                <div class="form-group">
                    <label>Poids à vide (kg) *</label>
                    <input type="number" ng-model="vehicle.weight_empty" ng-change="updateClassification()" required>
                </div>
                <div class="form-group">
                    <label>Poids total autorisé (kg) *</label>
                    <input type="number" ng-model="vehicle.weight_total" ng-change="updateClassification()" required>
                </div>
            </div>

            <div class="classification-info" ng-if="classification">
                <h4>📋 Classification douanière estimée</h4>
                <p><strong>Code TARES:</strong> {{ classification.commodity_code }} - {{ classification.description }}</p>
                
                <div ng-if="classification.needs_user_selection">
                    <p><strong>Sélectionnez la clé statistique: *</strong></p>
                    <label ng-repeat="(key, desc) in classification.available_keys" class="radio-label">
                        <input type="radio" ng-model="vehicle.statistical_key" ng-value="key" required>
                        {{ key }} - {{ desc }}
                    </label>
                </div>
                   <p ng-if="!classification.needs_user_selection">
                        <strong>Clé statistique:</strong> {{ vehicle.statistical_key }} - {{ classification.statistical_key_description }}
                    </p>
            </div>
            
            <div class="form-actions">
                <button type="button" class="btn btn-secondary" ng-click="step = 1">← Retour</button>
                <button type="button" class="btn btn-primary" ng-click="nextStep()" 
                             ng-disabled="vehicleForm.$invalid || (classification.needs_user_selection && !vehicle.statistical_key)">Continuer →</button>
            </div>
        </form>
				<div class="alert alert-danger"
						ng-if="ocrAttempted && !ocrLoading && !vehicle.vin && !vehicle.brand && !vehicle.model">
						Analyse de votre carte grise impossible. Veuillez vérifier le fichier et réessayer.
				</div>
    </div>
    
    <div ng-show="step === 3" class="step-content">
        <h2 style="text-align: center;">Informations pour la déclaration</h2>
        
        <form name="valuationForm" class="form" novalidate>
            <fieldset>
                <legend>Parties Impliquées</legend>
                <h4>Importateur / Destinataire</h4>
                <p class="disclaimer" style="margin-bottom: 1rem;">La personne ou l'entreprise qui importe le véhicule en Suisse.</p>
                <div class="form-row">
                    <div class="form-group">
                        <label>Nom ou raison sociale *</label>
                        <input type="text" ng-model="parties.importer.name" required>
                    </div>
                    <div class="form-group">
                        <label>Prénom</label>
                        <input type="text" ng-model="parties.importer.firstname" **ng-disabled="isCompany"** placeholder="Prénom">
                    </div>
                </div>
                <div class="form-group">
                    <label>Adresse (Rue et numéro)</label>
                    <input type="text" ng-model="parties.importer.address">
                </div>
                <div class="form-row">
                    <div class="form-group">
                        <label>NPA *</label>
                        <input type="text" ng-model="parties.importer.zip" required>
                    </div>
                    <div class="form-group">
                        <label>Ville *</label>
                        <input type="text" ng-model="parties.importer.city" required>
                    </div>
                </div>
                   <div class="form-group">
                        <label>Numéro IDE</label>
                        <input type="text" ng-model="parties.importer.ide" placeholder="CHE222251936 pour particulier (défaut)">
                    </div>
                <small>Par défaut, le Destinataire et le Déclarant sont identiques à l'Importateur.</small>
            </fieldset>

            <fieldset>
                <legend>Régime Douanier</legend>
                <div class="form-row">
                    <div class="form-group">
                        <label>Pays d'expédition *</label>
                        <select ng-model="declaration.dispatch_country" ng-options="code as name for (code, name) in countries" required><option value="">Sélectionner...</option></select>
                    </div>
                    <div class="form-group">
                        <label>Mode de transport *</label>
                        <select ng-model="declaration.transport_mode" ng-options="mode.value as mode.label for mode in transportModes" required></select>
                    </div>
                </div>
                   <label class="checkbox-label">
                        <input type="checkbox" ng-model="declaration.is_relocation">
                        Importation pour déménagement (Exemption TVA)
                    </label>
                   <label class="checkbox-label">
                        <input type="checkbox" ng-model="declaration.is_iva_exempt">
                        Exonéré de l'impôt sur les véhicules automobiles (IVA / RG 660)
                    </label>
            </fieldset>
            
            <fieldset>
                <legend>Transactions Financières</legend>
                <div class="form-row">
                    <div class="form-group">
                        <label>Prix d'achat du véhicule *</label>
                        <input type="number" step="0.01" ng-model="declaration.purchase_price" ng-change="calculateTotal()" required>
                    </div>
                    <div class="form-group">
                        <label>Monnaie de la facture *</label>
                        <select ng-model="declaration.purchase_currency" ng-change="calculateTotal()" ng-options="code as code for (code, name) in currencies" required></select>
                    </div>
                </div>
                <p><strong>Coûts additionnels (dans la même devise)</strong></p>
                <div class="form-row">
                    <div class="form-group">
                        <label>Frais de transport jusqu'à la frontière</label>
                        <input type="number" step="0.01" ng-model="declaration.transport_cost" ng-change="calculateTotal()">
                    </div>
                    <div class="form-group">
                        <label>Autres frais (ex: assurance)</label>
                        <input type="number" step="0.01" ng-model="declaration.other_costs" ng-change="calculateTotal()">
                    </div>
                </div>
                <div class="total-box" ng-if="vatValueCHF > 0">
                    <h3>Valeur totale (base de calcul TVA/IVA): ~ {{ vatValueCHF | number:0 }} CHF</h3>
                </div>
                
                </fieldset>

            <div class="form-actions">
                <button type="button" class="btn btn-secondary" ng-click="prevStep()">← Retour</button>
                <button type="button" class="btn btn-primary" ng-click="generateDeclaration()" 
                             ng-disabled="valuationForm.$invalid || generating">
                    <span ng-if="!generating">Générer la déclaration ✨</span>
                    <span ng-if="generating">Génération en cours...</span>
                </button>
            </div>
        </form>
    </div>
    
    <div ng-show="step === 4" class="step-content success-content">
        <div class="success-icon">✅</div>
        <h2>Déclaration générée avec succès !</h2>
        <p>Votre fichier XML a été téléchargé. Il est prêt à être importé sur le portail e-dec de la douane.</p>
        
        <div class="next-steps-box">
            <h3>Prochaines étapes :</h3>
            <ol>
                <li>Rendez-vous sur le portail e-dec web de l'Office fédéral de la douane.</li>
                <li>Authentifiez-vous et choisissez "Importer une déclaration".</li>
                <li>Importez le fichier XML que vous venez de télécharger.</li>
                <li>Vérifiez attentivement toutes les informations pré-remplies.</li>
                <li>Joignez les documents nécessaires (facture, carte grise).</li>
                <li>Soumettez votre déclaration.</li>
            </ol>
        </div>
        
        <div class="form-actions" style="justify-content: center;">
            <button class="btn btn-secondary" ng-click="resetForm()">Nouvelle déclaration</button>
            <a href="https://e-dec-web.ezv.admin.ch/webdec/main.xhtml" class="btn btn-primary" target="_blank">
                Aller au portail e-dec →
            </a>
        </div>
    </div>
</div>
----------------------------------------------------
package.json

{
  "name": "edec-vehicles",
  "version": "1.1.0",
  "description": "Application e-dec Véhicules avec AngularJS et ExpressJS",
  "main": "server/server.js",
  "scripts": {
    "start": "node server/server.js",
    "dev": "nodemon server/server.js"
  },
  "dependencies": {
    "axios": "^1.6.2",
    "bcryptjs": "^2.4.3",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2",
    "express-rate-limit": "^7.4.0",
    "express-validator": "^7.1.0",
    "helmet": "^7.1.0",
    "jsonwebtoken": "^9.0.2",
    "morgan": "^1.10.0",
    "multer": "^1.4.5-lts.1",
    "mysql2": "^3.6.5",
    "node-cron": "^3.0.3",
    "pdf-parse": "^1.1.1",
    "playwright": "^1.40.1",
    "sharp": "^0.33.4",
    "uuid": "^9.0.1",
    "winston": "^3.11.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.2"
  }
}
