Initial commit - Facturation SANTINOVA
This commit is contained in:
248
backend/src/routes/purchaseOrders.ts
Normal file
248
backend/src/routes/purchaseOrders.ts
Normal file
@@ -0,0 +1,248 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user