You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
635 lines
20 KiB
635 lines
20 KiB
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;
|
|
|