const { MenuCategory, Menu, sequelize } = require('../models/associations'); const { Op } = require('sequelize'); class MenuCategoryController { // Get all categories with search and pagination async getAllCategories(req, res) { try { const { page = 1, limit = 10, search = '', actif, sort_by = 'ordre', sort_order = 'ASC' } = req.query; const offset = (parseInt(page) - 1) * parseInt(limit); // Build where conditions const whereConditions = {}; if (search) { whereConditions[Op.or] = [ { nom: { [Op.like]: `%${search}%` } }, { description: { [Op.like]: `%${search}%` } } ]; } if (actif !== undefined) { whereConditions.actif = actif === 'true'; } // Validate sort fields const validSortFields = ['nom', 'ordre', 'created_at', 'updated_at']; const sortField = validSortFields.includes(sort_by) ? sort_by : 'ordre'; const sortOrder = ['ASC', 'DESC'].includes(sort_order.toUpperCase()) ? sort_order.toUpperCase() : 'ASC'; const { count, rows } = await MenuCategory.findAndCountAll({ where: whereConditions, include: [{ model: Menu, as: 'menus', // ✅ Utiliser l'alias défini dans associations.js attributes: ['id'], required: false }], order: [[sortField, sortOrder]], limit: parseInt(limit), offset: offset, distinct: true }); // Add menu count to each category const categoriesWithCount = rows.map(category => ({ ...category.toJSON(), menu_count: category.menus ? category.menus.length : 0, // ✅ Utiliser l'alias menus: undefined // Remove the menus array from response })); res.json({ success: true, data: { categories: categoriesWithCount, pagination: { currentPage: parseInt(page), totalPages: Math.ceil(count / parseInt(limit)), totalItems: count, itemsPerPage: parseInt(limit) } } }); } catch (error) { console.error('❌ Error in getAllCategories:', error); res.status(500).json({ success: false, message: 'Erreur lors de la récupération des catégories', error: error.message }); } } // Get active categories only (for dropdowns, etc.) async getActiveCategories(req, res) { try { const categories = await MenuCategory.findAll({ where: { actif: true }, order: [['ordre', 'ASC'], ['nom', 'ASC']], attributes: ['id', 'nom', 'description', 'ordre'] }); res.json({ success: true, data: categories }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la récupération des catégories actives', error: error.message }); } } // Get category by ID async getCategoryById(req, res) { try { const { id } = req.params; const category = await MenuCategory.findByPk(id, { include: [{ model: Menu, as: 'menus', // ✅ Utiliser l'alias attributes: ['id', 'nom', 'prix', 'actif'], required: false }] }); if (!category) { return res.status(404).json({ success: false, message: 'Catégorie non trouvée' }); } const categoryData = { ...category.toJSON(), menu_count: category.menus ? category.menus.length : 0 // ✅ Utiliser l'alias }; res.json({ success: true, data: categoryData }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la récupération de la catégorie', error: error.message }); } } // Get all menus in a category async getCategoryMenus(req, res) { try { const { id } = req.params; const { actif } = req.query; const category = await MenuCategory.findByPk(id); if (!category) { return res.status(404).json({ success: false, message: 'Catégorie non trouvée' }); } const whereConditions = { categorie_id: id }; // ✅ Utiliser le bon nom de colonne if (actif !== undefined) { whereConditions.actif = actif === 'true'; } const menus = await Menu.findAll({ where: whereConditions, order: [['nom', 'ASC']] }); res.json({ success: true, data: { category: { id: category.id, nom: category.nom, description: category.description }, menus: menus } }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la récupération des menus de la catégorie', error: error.message }); } } // Get category statistics async getCategoryStats(req, res) { try { const [total, active, inactive] = await Promise.all([ MenuCategory.count(), MenuCategory.count({ where: { actif: true } }), MenuCategory.count({ where: { actif: false } }) ]); // Get total menus across all categories const totalMenus = await Menu.count(); // Get categories with most menus const categoriesWithMenuCount = await MenuCategory.findAll({ attributes: [ 'id', 'nom', [sequelize.fn('COUNT', sequelize.col('menus.id')), 'menu_count'] // ✅ Utiliser l'alias ], include: [{ model: Menu, as: 'menus', // ✅ Utiliser l'alias attributes: [], required: false }], group: ['MenuCategory.id', 'MenuCategory.nom'], // ✅ Ajouter tous les champs non-agrégés order: [[sequelize.fn('COUNT', sequelize.col('menus.id')), 'DESC']], limit: 5 }); res.json({ success: true, data: { total, active, inactive, totalMenus, topCategories: categoriesWithMenuCount.map(cat => ({ id: cat.id, nom: cat.nom, menu_count: parseInt(cat.dataValues.menu_count || 0) })) } }); } catch (error) { console.error('❌ Error in getCategoryStats:', error); res.status(500).json({ success: false, message: 'Erreur lors de la récupération des statistiques', error: error.message }); } } // Create new category async createCategory(req, res) { try { const { nom, description, ordre = 0, actif = true } = req.body; // Validation if (!nom || nom.trim().length === 0) { return res.status(400).json({ success: false, message: 'Le nom de la catégorie est requis' }); } if (nom.length > 100) { return res.status(400).json({ success: false, message: 'Le nom ne peut pas dépasser 100 caractères' }); } // Check if category name already exists const existingCategory = await MenuCategory.findOne({ where: { nom: nom.trim() } }); if (existingCategory) { return res.status(400).json({ success: false, message: 'Une catégorie avec ce nom existe déjà' }); } // If no order specified, set it to be last let finalOrder = ordre; if (!ordre || ordre === 0) { const maxOrder = await MenuCategory.max('ordre') || 0; finalOrder = maxOrder + 1; } const category = await MenuCategory.create({ nom: nom.trim(), description: description?.trim(), ordre: finalOrder, actif }); res.status(201).json({ success: true, message: 'Catégorie créée avec succès', data: category }); } catch (error) { console.error('❌ Error in createCategory:', error); res.status(500).json({ success: false, message: 'Erreur lors de la création de la catégorie', error: error.message }); } } // Update category async updateCategory(req, res) { try { const { id } = req.params; const { nom, description, ordre, actif } = req.body; const category = await MenuCategory.findByPk(id); if (!category) { return res.status(404).json({ success: false, message: 'Catégorie non trouvée' }); } // Validation if (!nom || nom.trim().length === 0) { return res.status(400).json({ success: false, message: 'Le nom de la catégorie est requis' }); } if (nom.length > 100) { return res.status(400).json({ success: false, message: 'Le nom ne peut pas dépasser 100 caractères' }); } // Check if category name already exists (excluding current category) const existingCategory = await MenuCategory.findOne({ where: { nom: nom.trim(), id: { [Op.ne]: id } } }); if (existingCategory) { return res.status(400).json({ success: false, message: 'Une catégorie avec ce nom existe déjà' }); } // Update category await category.update({ nom: nom.trim(), description: description?.trim(), ordre: ordre !== undefined ? ordre : category.ordre, actif: actif !== undefined ? actif : category.actif }); res.json({ success: true, message: 'Catégorie mise à jour avec succès', data: category }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la mise à jour de la catégorie', error: error.message }); } } // Toggle category status async toggleCategoryStatus(req, res) { try { const { id } = req.params; const category = await MenuCategory.findByPk(id); if (!category) { return res.status(404).json({ success: false, message: 'Catégorie non trouvée' }); } await category.update({ actif: !category.actif }); res.json({ success: true, message: `Catégorie ${category.actif ? 'activée' : 'désactivée'} avec succès`, data: category }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la mise à jour du statut', error: error.message }); } } // Delete category async deleteCategory(req, res) { try { const { id } = req.params; const category = await MenuCategory.findByPk(id, { include: [{ model: Menu, as: 'menus', // ✅ Utiliser l'alias attributes: ['id'], required: false }] }); if (!category) { return res.status(404).json({ success: false, message: 'Catégorie non trouvée' }); } // Check if category has associated menus if (category.menus && category.menus.length > 0) { // ✅ Utiliser l'alias return res.status(400).json({ success: false, message: `Impossible de supprimer la catégorie. Elle contient ${category.menus.length} menu(s). Veuillez d'abord supprimer ou déplacer les menus.` }); } await category.destroy(); res.json({ success: true, message: 'Catégorie supprimée avec succès' }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la suppression de la catégorie', error: error.message }); } } // Reorder categories async reorderCategories(req, res) { const transaction = await sequelize.transaction(); try { const { categories } = req.body; if (!Array.isArray(categories) || categories.length === 0) { return res.status(400).json({ success: false, message: 'Liste des catégories requise' }); } // Validate each category object for (const cat of categories) { if (!cat.id || cat.ordre === undefined) { return res.status(400).json({ success: false, message: 'Chaque catégorie doit avoir un ID et un ordre' }); } } // Update each category's order const updatePromises = categories.map(cat => MenuCategory.update( { ordre: cat.ordre }, { where: { id: cat.id }, transaction } ) ); await Promise.all(updatePromises); await transaction.commit(); res.json({ success: true, message: 'Ordre des catégories mis à jour avec succès' }); } catch (error) { await transaction.rollback(); res.status(500).json({ success: false, message: 'Erreur lors de la réorganisation des catégories', error: error.message }); } } } module.exports = new MenuCategoryController();