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.
 

495 lines
16 KiB

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();