const Table = require('../models/Table'); const { Op } = require('sequelize'); class TableController { // Get all tables with optional filtering static async getAllTables(req, res) { try { console.log('🔍 TableController.getAllTables - Début'); const { status, location, capacity_min, capacity_max, page = 1, limit = 10, sort = 'id', order = 'ASC' } = req.query; const offset = (page - 1) * limit; const whereClause = {}; // Apply filters if (status) { whereClause.status = status; } if (location) { whereClause.location = { [Op.like]: `%${location}%` }; } if (capacity_min || capacity_max) { whereClause.capacity = {}; if (capacity_min) whereClause.capacity[Op.gte] = parseInt(capacity_min); if (capacity_max) whereClause.capacity[Op.lte] = parseInt(capacity_max); } console.log('Where conditions:', whereClause); const { count, rows } = await Table.findAndCountAll({ where: whereClause, limit: parseInt(limit), offset: parseInt(offset), order: [[sort, order.toUpperCase()]] }); console.log(`✅ Found ${count} tables, returning ${rows.length} tables`); res.json({ success: true, data: rows, pagination: { total: count, page: parseInt(page), limit: parseInt(limit), totalPages: Math.ceil(count / limit) } }); } catch (error) { console.error('❌ Error in getAllTables:', error); res.status(500).json({ success: false, error: 'Failed to fetch tables', message: error.message }); } } // Get table by ID static async getTableById(req, res) { try { const { id } = req.params; if (!id || isNaN(id)) { return res.status(400).json({ success: false, error: 'Valid table ID is required' }); } const table = await Table.findByPk(id); if (!table) { return res.status(404).json({ success: false, error: 'Table not found' }); } res.json({ success: true, data: table }); } catch (error) { console.error('❌ Error in getTableById:', error); res.status(500).json({ success: false, error: 'Failed to fetch table', message: error.message }); } } // Create new table static async createTable(req, res) { try { const { nom, capacity, status, location } = req.body; // Validation if (!nom) { return res.status(400).json({ success: false, error: 'Table name is required' }); } if (nom.length > 100) { return res.status(400).json({ success: false, error: 'Table name must be less than 100 characters' }); } if (capacity && (capacity < 1 || capacity > 20)) { return res.status(400).json({ success: false, error: 'Capacity must be between 1 and 20' }); } if (status && !['available', 'occupied', 'reserved', 'maintenance'].includes(status)) { return res.status(400).json({ success: false, error: 'Status must be: available, occupied, reserved, or maintenance' }); } if (location && location.length > 50) { return res.status(400).json({ success: false, error: 'Location must be less than 50 characters' }); } // Check if table name already exists const existingTable = await Table.findOne({ where: { nom } }); if (existingTable) { return res.status(409).json({ success: false, error: 'Table name already exists' }); } const table = await Table.create({ nom, capacity: capacity || 4, status: status || 'available', location: location || null }); res.status(201).json({ success: true, data: table, message: 'Table created successfully' }); } catch (error) { console.error('❌ Error in createTable:', error); if (error.name === 'SequelizeValidationError') { return res.status(400).json({ success: false, error: 'Validation error', details: error.errors.map(err => ({ field: err.path, message: err.message })) }); } if (error.name === 'SequelizeUniqueConstraintError') { return res.status(409).json({ success: false, error: 'Table name already exists' }); } res.status(500).json({ success: false, error: 'Failed to create table', message: error.message }); } } // Update table static async updateTable(req, res) { try { const { id } = req.params; const { nom, capacity, status, location } = req.body; if (!id || isNaN(id)) { return res.status(400).json({ success: false, error: 'Valid table ID is required' }); } const table = await Table.findByPk(id); if (!table) { return res.status(404).json({ success: false, error: 'Table not found' }); } // Validation if (nom !== undefined) { if (!nom || nom.length === 0) { return res.status(400).json({ success: false, error: 'Table name cannot be empty' }); } if (nom.length > 100) { return res.status(400).json({ success: false, error: 'Table name must be less than 100 characters' }); } // Check if new name already exists (excluding current table) const existingTable = await Table.findOne({ where: { nom, id: { [Op.ne]: id } } }); if (existingTable) { return res.status(409).json({ success: false, error: 'Table name already exists' }); } } if (capacity !== undefined && (capacity < 1 || capacity > 20)) { return res.status(400).json({ success: false, error: 'Capacity must be between 1 and 20' }); } if (status && !['available', 'occupied', 'reserved', 'maintenance'].includes(status)) { return res.status(400).json({ success: false, error: 'Status must be: available, occupied, reserved, or maintenance' }); } if (location !== undefined && location && location.length > 50) { return res.status(400).json({ success: false, error: 'Location must be less than 50 characters' }); } // Update fields const updateData = {}; if (nom !== undefined) updateData.nom = nom; if (capacity !== undefined) updateData.capacity = capacity; if (status !== undefined) updateData.status = status; if (location !== undefined) updateData.location = location; await table.update(updateData); res.json({ success: true, data: table, message: 'Table updated successfully' }); } catch (error) { console.error('❌ Error in updateTable:', error); if (error.name === 'SequelizeValidationError') { return res.status(400).json({ success: false, error: 'Validation error', details: error.errors.map(err => ({ field: err.path, message: err.message })) }); } if (error.name === 'SequelizeUniqueConstraintError') { return res.status(409).json({ success: false, error: 'Table name already exists' }); } res.status(500).json({ success: false, error: 'Failed to update table', message: error.message }); } } // Delete table static async deleteTable(req, res) { try { const { id } = req.params; if (!id || isNaN(id)) { return res.status(400).json({ success: false, error: 'Valid table ID is required' }); } const table = await Table.findByPk(id); if (!table) { return res.status(404).json({ success: false, error: 'Table not found' }); } // Check if table is currently occupied or reserved if (table.status === 'occupied' || table.status === 'reserved') { return res.status(400).json({ success: false, error: 'Cannot delete table that is currently occupied or reserved' }); } await table.destroy(); res.json({ success: true, message: 'Table deleted successfully' }); } catch (error) { console.error('❌ Error in deleteTable:', error); res.status(500).json({ success: false, error: 'Failed to delete table', message: error.message }); } } // Get tables by status static async getTablesByStatus(req, res) { try { const { status } = req.params; const validStatuses = ['available', 'occupied', 'reserved', 'maintenance']; if (!validStatuses.includes(status)) { return res.status(400).json({ success: false, error: `Invalid status. Must be: ${validStatuses.join(', ')}` }); } const tables = await Table.findAll({ where: { status }, order: [['nom', 'ASC']] }); res.json({ success: true, data: tables, count: tables.length }); } catch (error) { console.error('❌ Error in getTablesByStatus:', error); res.status(500).json({ success: false, error: 'Failed to fetch tables by status', message: error.message }); } } // Update table status static async updateTableStatus(req, res) { try { const { id } = req.params; const { status } = req.body; if (!id || isNaN(id)) { return res.status(400).json({ success: false, error: 'Valid table ID is required' }); } const validStatuses = ['available', 'occupied', 'reserved', 'maintenance']; if (!status || !validStatuses.includes(status)) { return res.status(400).json({ success: false, error: `Valid status is required (${validStatuses.join(', ')})` }); } const table = await Table.findByPk(id); if (!table) { return res.status(404).json({ success: false, error: 'Table not found' }); } const oldStatus = table.status; await table.update({ status }); res.json({ success: true, data: table, message: `Table status updated from ${oldStatus} to ${status}` }); } catch (error) { console.error('❌ Error in updateTableStatus:', error); res.status(500).json({ success: false, error: 'Failed to update table status', message: error.message }); } } // Get table statistics static async getTableStats(req, res) { try { const totalTables = await Table.count(); const statusStats = await Table.findAll({ attributes: [ 'status', [Table.sequelize.fn('COUNT', Table.sequelize.col('status')), 'count'] ], group: ['status'], raw: true }); const capacityStats = await Table.findOne({ attributes: [ [Table.sequelize.fn('SUM', Table.sequelize.col('capacity')), 'total_capacity'], [Table.sequelize.fn('AVG', Table.sequelize.col('capacity')), 'avg_capacity'], [Table.sequelize.fn('MIN', Table.sequelize.col('capacity')), 'min_capacity'], [Table.sequelize.fn('MAX', Table.sequelize.col('capacity')), 'max_capacity'] ], raw: true }); const locationStats = await Table.findAll({ where: { location: { [Op.ne]: null } }, attributes: [ 'location', [Table.sequelize.fn('COUNT', Table.sequelize.col('location')), 'count'] ], group: ['location'], raw: true }); res.json({ success: true, data: { total: totalTables, statusBreakdown: statusStats.reduce((acc, stat) => { acc[stat.status] = parseInt(stat.count); return acc; }, {}), locationBreakdown: locationStats.reduce((acc, stat) => { acc[stat.location] = parseInt(stat.count); return acc; }, {}), capacityStats: { total: parseInt(capacityStats?.total_capacity) || 0, average: parseFloat(capacityStats?.avg_capacity) || 0, min: parseInt(capacityStats?.min_capacity) || 0, max: parseInt(capacityStats?.max_capacity) || 0 } } }); } catch (error) { console.error('❌ Error in getTableStats:', error); res.status(500).json({ success: false, error: 'Failed to get table statistics', message: error.message }); } } // Search tables static async searchTables(req, res) { try { const { q, status, location, capacity_min, limit = 10 } = req.query; if (!q || q.length < 2) { return res.status(400).json({ success: false, error: 'Search term must be at least 2 characters long' }); } const whereClause = { [Op.or]: [ { nom: { [Op.like]: `%${q}%` } }, { location: { [Op.like]: `%${q}%` } } ] }; if (status) { whereClause.status = status; } if (location) { whereClause.location = { [Op.like]: `%${location}%` }; } if (capacity_min) { whereClause.capacity = { [Op.gte]: parseInt(capacity_min) }; } const tables = await Table.findAll({ where: whereClause, limit: parseInt(limit), order: [['nom', 'ASC']] }); res.json({ success: true, data: tables, count: tables.length }); } catch (error) { console.error('❌ Error in searchTables:', error); res.status(500).json({ success: false, error: 'Failed to search tables', message: error.message }); } } // Get tables by location static async getTablesByLocation(req, res) { try { const { location } = req.params; if (!location) { return res.status(400).json({ success: false, error: 'Location parameter is required' }); } const tables = await Table.findAll({ where: { location: { [Op.like]: `%${location}%` } }, order: [['nom', 'ASC']] }); res.json({ success: true, data: tables, count: tables.length }); } catch (error) { console.error('❌ Error in getTablesByLocation:', error); res.status(500).json({ success: false, error: 'Failed to fetch tables by location', message: error.message }); } } // Get available tables with optional capacity filter static async getAvailableTables(req, res) { try { const { capacity_min, location } = req.query; const whereClause = { status: 'available' }; if (capacity_min) { whereClause.capacity = { [Op.gte]: parseInt(capacity_min) }; } if (location) { whereClause.location = { [Op.like]: `%${location}%` }; } const tables = await Table.findAll({ where: whereClause, order: [['capacity', 'ASC'], ['nom', 'ASC']] }); res.json({ success: true, data: tables, count: tables.length }); } catch (error) { console.error('❌ Error in getAvailableTables:', error); res.status(500).json({ success: false, error: 'Failed to fetch available tables', message: error.message }); } } } module.exports = TableController;