import { Router, Response } from 'express'; import { getPool } from '../config/database'; import { authenticate, AuthRequest } from '../middleware/auth'; import { logAction } from '../services/auditService'; const router = Router(); // GET /api/purchase-orders router.get('/', authenticate, async (req: AuthRequest, res: Response) => { try { const pool = getPool(); const { status, supplierId, search, page = '1', limit = '20' } = req.query; let query = 'SELECT po.*, s.name as supplier_display_name FROM purchase_orders po LEFT JOIN suppliers s ON po.supplier_id = s.id WHERE 1=1'; let countQuery = 'SELECT COUNT(*) as total FROM purchase_orders po WHERE 1=1'; const params: any[] = []; const countParams: any[] = []; if (status) { query += ' AND po.status = ?'; countQuery += ' AND po.status = ?'; params.push(status); countParams.push(status); } if (supplierId) { query += ' AND po.supplier_id = ?'; countQuery += ' AND po.supplier_id = ?'; params.push(supplierId); countParams.push(supplierId); } if (search) { query += ' AND (po.order_number LIKE ? OR po.supplier_name LIKE ? OR po.description LIKE ?)'; countQuery += ' AND (po.order_number LIKE ? OR po.supplier_name LIKE ? OR po.description LIKE ?)'; const s = `%${search}%`; params.push(s, s, s); countParams.push(s, s, s); } query += ' ORDER BY po.created_at DESC'; const pageNum = parseInt(page as string) || 1; const limitNum = parseInt(limit as string) || 20; const offset = (pageNum - 1) * limitNum; query += ' LIMIT ? OFFSET ?'; params.push(limitNum, offset); const [rows]: any = await pool.execute(query, params); const [countRows]: any = await pool.execute(countQuery, countParams); const orders = rows.map((po: any) => ({ id: po.id, orderNumber: po.order_number, supplierId: po.supplier_id, supplierName: po.supplier_name || po.supplier_display_name, orderDate: po.order_date, expectedDeliveryDate: po.expected_delivery_date, amountHT: po.amount_ht, amountTVA: po.amount_tva, amountTTC: po.amount_ttc, status: po.status, description: po.description, notes: po.notes, createdBy: po.created_by, createdAt: po.created_at, updatedAt: po.updated_at, })); res.json({ data: orders, pagination: { total: countRows[0].total, page: pageNum, limit: limitNum, totalPages: Math.ceil(countRows[0].total / limitNum), }, }); } catch (error: any) { console.error('Erreur liste BC:', error); res.status(500).json({ error: 'Erreur serveur' }); } }); // GET /api/purchase-orders/:id router.get('/:id', authenticate, async (req: AuthRequest, res: Response) => { try { const pool = getPool(); const [rows]: any = await pool.execute( 'SELECT po.*, s.name as supplier_display_name FROM purchase_orders po LEFT JOIN suppliers s ON po.supplier_id = s.id WHERE po.id = ?', [req.params.id] ); if (!rows.length) { return res.status(404).json({ error: 'Bon de commande non trouvé' }); } const po = rows[0]; const [lines]: any = await pool.execute( 'SELECT * FROM purchase_order_lines WHERE purchase_order_id = ? ORDER BY line_order ASC', [req.params.id] ); // Get linked invoices const [linkedInvoices]: any = await pool.execute( 'SELECT id, invoice_number, amount_ttc, status, matching_status FROM invoices WHERE purchase_order_id = ?', [req.params.id] ); res.json({ id: po.id, orderNumber: po.order_number, supplierId: po.supplier_id, supplierName: po.supplier_name || po.supplier_display_name, orderDate: po.order_date, expectedDeliveryDate: po.expected_delivery_date, amountHT: po.amount_ht, amountTVA: po.amount_tva, amountTTC: po.amount_ttc, status: po.status, description: po.description, notes: po.notes, createdBy: po.created_by, createdAt: po.created_at, updatedAt: po.updated_at, lines: lines.map((l: any) => ({ id: l.id, description: l.description, quantity: l.quantity, unitPrice: l.unit_price, amountHT: l.amount_ht, lineOrder: l.line_order, })), linkedInvoices: linkedInvoices.map((i: any) => ({ id: i.id, invoiceNumber: i.invoice_number, amountTTC: i.amount_ttc, status: i.status, matchingStatus: i.matching_status, })), }); } catch (error: any) { console.error('Erreur get BC:', error); res.status(500).json({ error: 'Erreur serveur' }); } }); // POST /api/purchase-orders router.post('/', authenticate, async (req: AuthRequest, res: Response) => { try { const { orderNumber, supplierId, supplierName, orderDate, expectedDeliveryDate, amountHT, amountTVA, amountTTC, description, notes, lines } = req.body; if (!orderNumber || !orderDate) { return res.status(400).json({ error: 'Numéro de commande et date requis' }); } const pool = getPool(); const [result]: any = await pool.execute( `INSERT INTO purchase_orders (order_number, supplier_id, supplier_name, order_date, expected_delivery_date, amount_ht, amount_tva, amount_ttc, description, notes, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [orderNumber, supplierId || null, supplierName || null, orderDate, expectedDeliveryDate || null, amountHT || null, amountTVA || null, amountTTC || null, description || null, notes || null, req.user!.id] ); const poId = result.insertId; // Insert lines if (lines && lines.length > 0) { for (let idx = 0; idx < lines.length; idx++) { const line = lines[idx]; await pool.execute( `INSERT INTO purchase_order_lines (purchase_order_id, description, quantity, unit_price, amount_ht, line_order) VALUES (?, ?, ?, ?, ?, ?)`, [poId, line.description || null, line.quantity || null, line.unitPrice || null, line.amountHT || null, idx] ); } } await logAction('purchase_order', poId, 'creation', req.user!.id, `${req.user!.firstName} ${req.user!.lastName}`); res.status(201).json({ id: poId, orderNumber }); } catch (error: any) { if (error.code === 'ER_DUP_ENTRY') { return res.status(409).json({ error: 'Ce numéro de commande existe déjà' }); } console.error('Erreur création BC:', error); res.status(500).json({ error: 'Erreur serveur' }); } }); // PUT /api/purchase-orders/:id router.put('/:id', authenticate, async (req: AuthRequest, res: Response) => { try { const { orderNumber, supplierId, supplierName, orderDate, expectedDeliveryDate, amountHT, amountTVA, amountTTC, status, description, notes, lines } = req.body; const pool = getPool(); await pool.execute( `UPDATE purchase_orders SET order_number = ?, supplier_id = ?, supplier_name = ?, order_date = ?, expected_delivery_date = ?, amount_ht = ?, amount_tva = ?, amount_ttc = ?, status = ?, description = ?, notes = ? WHERE id = ?`, [orderNumber, supplierId || null, supplierName || null, orderDate, expectedDeliveryDate || null, amountHT || null, amountTVA || null, amountTTC || null, status || 'brouillon', description || null, notes || null, req.params.id] ); // Update lines if provided if (lines) { await pool.execute('DELETE FROM purchase_order_lines WHERE purchase_order_id = ?', [req.params.id]); for (let idx = 0; idx < lines.length; idx++) { const line = lines[idx]; await pool.execute( `INSERT INTO purchase_order_lines (purchase_order_id, description, quantity, unit_price, amount_ht, line_order) VALUES (?, ?, ?, ?, ?, ?)`, [req.params.id, line.description || null, line.quantity || null, line.unitPrice || null, line.amountHT || null, idx] ); } } await logAction('purchase_order', parseInt(req.params.id), 'modification', req.user!.id, `${req.user!.firstName} ${req.user!.lastName}`); res.json({ message: 'Bon de commande mis à jour' }); } catch (error: any) { console.error('Erreur update BC:', error); res.status(500).json({ error: 'Erreur serveur' }); } }); // DELETE /api/purchase-orders/:id router.delete('/:id', authenticate, async (req: AuthRequest, res: Response) => { try { const pool = getPool(); await pool.execute('DELETE FROM purchase_order_lines WHERE purchase_order_id = ?', [req.params.id]); await pool.execute('DELETE FROM purchase_orders WHERE id = ?', [req.params.id]); await logAction('purchase_order', parseInt(req.params.id), 'suppression', req.user!.id, `${req.user!.firstName} ${req.user!.lastName}`); res.json({ message: 'Bon de commande supprimé' }); } catch (error: any) { console.error('Erreur suppression BC:', error); res.status(500).json({ error: 'Erreur serveur' }); } }); export default router;