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