// controllers/commandesController.js const Commande = require('../models/Commande'); const CommandeItem = require('../models/CommandeItem'); const Menu = require('../models/Menu'); const { Op } = require('sequelize'); const sequelize = require('../config/database'); // Helper function to validate and convert numbers const validateNumber = (value, defaultValue = 0) => { const num = parseFloat(value); return isNaN(num) ? defaultValue : num; }; // Helper function to validate integer const validateInteger = (value, defaultValue = 1) => { const num = parseInt(value); return isNaN(num) ? defaultValue : num; }; // Get all commandes with menu details const getAllCommandes = async (req, res) => { try { const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; const offset = (page - 1) * limit; const where = {}; // Filters if (req.query.statut) { where.statut = req.query.statut; } if (req.query.client_id) { where.client_id = req.query.client_id; } if (req.query.table_id) { where.table_id = req.query.table_id; } if (req.query.serveur) { where.serveur = { [Op.like]: `%${req.query.serveur}%` }; } if (req.query.date_debut && req.query.date_fin) { where.date_commande = { [Op.between]: [req.query.date_debut, req.query.date_fin] }; } const { count, rows } = await Commande.findAndCountAll({ where, limit, offset, order: [['date_commande', 'DESC']] }); // Fetch items with menu details for each commande const commandeIds = rows.map(c => c.id); let itemsWithMenus = []; if (commandeIds.length > 0) { itemsWithMenus = await sequelize.query(` SELECT ci.*, m.nom as menu_nom, m.commentaire as menu_description, m.prix as menu_prix_actuel FROM commande_items ci LEFT JOIN menus m ON ci.menu_id = m.id WHERE ci.commande_id IN (${commandeIds.join(',')}) ORDER BY ci.id `, { type: sequelize.QueryTypes.SELECT }); } // Group items by commande_id const itemsByCommandeId = itemsWithMenus.reduce((acc, item) => { if (!acc[item.commande_id]) { acc[item.commande_id] = []; } acc[item.commande_id].push(item); return acc; }, {}); // Add items to each commande rows.forEach(commande => { commande.dataValues.items = itemsByCommandeId[commande.id] || []; }); res.status(200).json({ success: true, data: { commandes: rows, pagination: { currentPage: page, totalPages: Math.ceil(count / limit), totalItems: count, itemsPerPage: limit } } }); } catch (error) { console.error('Error in getAllCommandes:', error); res.status(500).json({ success: false, message: 'Erreur lors de la récupération des commandes', error: error.message }); } }; // Get commande by ID with menu details const getCommandeById = async (req, res) => { try { const commande = await Commande.findByPk(req.params.id); if (!commande) { return res.status(404).json({ success: false, message: 'Commande non trouvée' }); } // Fetch items with menu details const itemsWithMenus = await sequelize.query(` SELECT ci.*, m.nom as menu_nom, m.commentaire as menu_description, m.prix as menu_prix_actuel FROM commande_items ci LEFT JOIN menus m ON ci.menu_id = m.id WHERE ci.commande_id = :commandeId ORDER BY ci.id `, { replacements: { commandeId: req.params.id }, type: sequelize.QueryTypes.SELECT }); commande.dataValues.items = itemsWithMenus; res.status(200).json({ success: true, data: commande }); } catch (error) { console.error('Error in getCommandeById:', error); res.status(500).json({ success: false, message: 'Erreur lors de la récupération de la commande', error: error.message }); } }; // Create new commande with items (fetch prices from menu) const createCommande = async (req, res) => { let transaction; try { // Start transaction transaction = await sequelize.transaction(); console.log('Received data:', JSON.stringify(req.body, null, 2)); const { client_id, table_id, reservation_id, numero_commande, statut, total_ht, total_tva, total_ttc, mode_paiement, commentaires, serveur, date_commande, date_service, items // Array of items with menu_id and quantite } = req.body; // Validate required fields if (!table_id) { return res.status(400).json({ success: false, message: 'table_id est requis' }); } // Create the commande first const commandeData = { client_id: client_id || null, table_id: validateInteger(table_id), reservation_id: reservation_id || null, numero_commande: numero_commande || null, statut: statut || 'en_attente', total_ht: validateNumber(total_ht, 0).toFixed(2), total_tva: validateNumber(total_tva, 0).toFixed(2), total_ttc: validateNumber(total_ttc, 0).toFixed(2), mode_paiement: mode_paiement || null, commentaires: commentaires || null, serveur: serveur || null, date_commande: date_commande || new Date(), date_service: date_service || null }; console.log('Validated commande data:', JSON.stringify(commandeData, null, 2)); const newCommande = await Commande.create(commandeData, { transaction }); // If items are provided, create them with prices from menu if (items && Array.isArray(items) && items.length > 0) { console.log('Processing items:', JSON.stringify(items, null, 2)); const commandeItems = []; let calculatedTotal = 0; // Get all menu IDs from items const menuIds = items.map(item => validateInteger(item.menu_id)).filter(id => id > 0); if (menuIds.length === 0) { return res.status(400).json({ success: false, message: 'Aucun menu_id valide fourni' }); } // Fetch menu prices const menus = await Menu.findAll({ where: { id: { [Op.in]: menuIds } }, attributes: ['id', 'nom', 'prix', 'disponible'], transaction }); // Create a map of menu_id to menu data const menuMap = menus.reduce((acc, menu) => { acc[menu.id] = menu; return acc; }, {}); // Process each item for (const item of items) { const menu_id = validateInteger(item.menu_id); const quantite = validateInteger(item.quantite, 1); if (menu_id === 0) { return res.status(400).json({ success: false, message: 'menu_id invalide', invalidItem: item }); } // Check if menu exists const menu = menuMap[menu_id]; if (!menu) { return res.status(400).json({ success: false, message: `Menu avec ID ${menu_id} n'existe pas`, invalidItem: item }); } // Check if menu is available if (!menu.disponible) { return res.status(400).json({ success: false, message: `Menu "${menu.nom}" n'est plus disponible`, invalidItem: item }); } // Use price from menu table const prix_unitaire = validateNumber(menu.prix, 0); const total_item = prix_unitaire * quantite; if (prix_unitaire === 0) { return res.status(400).json({ success: false, message: `Prix invalide pour le menu "${menu.nom}"`, invalidItem: item }); } const validatedItem = { commande_id: newCommande.id, menu_id: menu_id, quantite: quantite, prix_unitaire: prix_unitaire.toFixed(2), total_item: total_item.toFixed(2), commentaires: item.commentaires || null, statut: item.statut || 'commande' }; commandeItems.push(validatedItem); calculatedTotal += total_item; } console.log('Validated items:', JSON.stringify(commandeItems, null, 2)); await CommandeItem.bulkCreate(commandeItems, { transaction }); // Auto-calculate totals if not provided if (validateNumber(total_ttc, 0) === 0) { await newCommande.update({ total_ht: calculatedTotal.toFixed(2), total_ttc: calculatedTotal.toFixed(2) }, { transaction }); } } // Commit the transaction await transaction.commit(); // Now fetch the created commande with items and menu details (outside of transaction) const createdCommande = await Commande.findByPk(newCommande.id); const createdItemsWithMenus = await sequelize.query(` SELECT ci.*, m.nom as menu_nom, m.commentaire as menu_description, m.prix as menu_prix_actuel FROM commande_items ci LEFT JOIN menus m ON ci.menu_id = m.id WHERE ci.commande_id = :commandeId ORDER BY ci.id `, { replacements: { commandeId: newCommande.id }, type: sequelize.QueryTypes.SELECT }); createdCommande.dataValues.items = createdItemsWithMenus; res.status(201).json({ success: true, message: 'Commande créée avec succès', data: createdCommande }); } catch (error) { // Only rollback if transaction exists and hasn't been finished if (transaction && !transaction.finished) { try { await transaction.rollback(); } catch (rollbackError) { console.error('Error rolling back transaction:', rollbackError); } } console.error('Error in createCommande:', error); res.status(400).json({ success: false, message: 'Erreur lors de la création de la commande', error: error.message }); } }; // Update commande (also fetch prices from menu) const updateCommande = async (req, res) => { let transaction; try { transaction = await sequelize.transaction(); const commandeId = req.params.id; const { items, ...commandeData } = req.body; const commande = await Commande.findByPk(commandeId); if (!commande) { return res.status(404).json({ success: false, message: 'Commande non trouvée' }); } // Validate numeric fields in commandeData if (commandeData.total_ht !== undefined) { commandeData.total_ht = validateNumber(commandeData.total_ht, 0).toFixed(2); } if (commandeData.total_tva !== undefined) { commandeData.total_tva = validateNumber(commandeData.total_tva, 0).toFixed(2); } if (commandeData.total_ttc !== undefined) { commandeData.total_ttc = validateNumber(commandeData.total_ttc, 0).toFixed(2); } // Update commande await commande.update(commandeData, { transaction }); // If items are provided, update them with current menu prices if (items && Array.isArray(items) && items.length > 0) { // Delete existing items await CommandeItem.destroy({ where: { commande_id: commandeId }, transaction }); // Get menu prices const menuIds = items.map(item => validateInteger(item.menu_id)).filter(id => id > 0); const menus = await Menu.findAll({ where: { id: { [Op.in]: menuIds } }, attributes: ['id', 'nom', 'prix', 'est_disponible'], transaction }); const menuMap = menus.reduce((acc, menu) => { acc[menu.id] = menu; return acc; }, {}); // Create new items with validation const commandeItems = []; let calculatedTotal = 0; for (const item of items) { const menu_id = validateInteger(item.menu_id); const quantite = validateInteger(item.quantite, 1); const menu = menuMap[menu_id]; if (!menu) { return res.status(400).json({ success: false, message: `Menu avec ID ${menu_id} n'existe pas` }); } const prix_unitaire = validateNumber(menu.prix, 0); const total_item = prix_unitaire * quantite; const validatedItem = { commande_id: commandeId, menu_id: menu_id, quantite: quantite, prix_unitaire: prix_unitaire.toFixed(2), total_item: total_item.toFixed(2), commentaires: item.commentaires || null, statut: item.statut || 'commande' }; commandeItems.push(validatedItem); calculatedTotal += total_item; } await CommandeItem.bulkCreate(commandeItems, { transaction }); // Recalculate totals if not provided if (!commandeData.total_ttc) { await commande.update({ total_ht: calculatedTotal.toFixed(2), total_ttc: calculatedTotal.toFixed(2) }, { transaction }); } } await transaction.commit(); // Fetch updated commande with items and menu details (outside of transaction) const updatedCommande = await Commande.findByPk(commandeId); const updatedItemsWithMenus = await sequelize.query(` SELECT ci.*, m.nom as menu_nom, m.commentaire as menu_description, m.prix as menu_prix_actuel FROM commande_items ci LEFT JOIN menus m ON ci.menu_id = m.id WHERE ci.commande_id = :commandeId ORDER BY ci.id `, { replacements: { commandeId }, type: sequelize.QueryTypes.SELECT }); updatedCommande.dataValues.items = updatedItemsWithMenus; res.status(200).json({ success: true, message: 'Commande mise à jour avec succès', data: updatedCommande }); } catch (error) { // Only rollback if transaction exists and hasn't been finished if (transaction && !transaction.finished) { try { await transaction.rollback(); } catch (rollbackError) { console.error('Error rolling back transaction:', rollbackError); } } console.error('Error in updateCommande:', error); res.status(400).json({ success: false, message: 'Erreur lors de la mise à jour de la commande', error: error.message }); } }; // Delete commande const deleteCommande = async (req, res) => { let transaction; try { transaction = await sequelize.transaction(); const commandeId = req.params.id; const commande = await Commande.findByPk(commandeId); if (!commande) { return res.status(404).json({ success: false, message: 'Commande non trouvée' }); } // Delete items first await CommandeItem.destroy({ where: { commande_id: commandeId }, transaction }); // Delete commande await commande.destroy({ transaction }); await transaction.commit(); res.status(200).json({ success: true, message: 'Commande supprimée avec succès' }); } catch (error) { if (transaction && !transaction.finished) { try { await transaction.rollback(); } catch (rollbackError) { console.error('Error rolling back transaction:', rollbackError); } } console.error('Error in deleteCommande:', error); res.status(500).json({ success: false, message: 'Erreur lors de la suppression de la commande', error: error.message }); } }; // Update commande status const updateStatut = async (req, res) => { try { const commandeId = req.params.id; const { statut } = req.body; if (!statut) { return res.status(400).json({ success: false, message: 'Le statut est requis' }); } const commande = await Commande.findByPk(commandeId); if (!commande) { return res.status(404).json({ success: false, message: 'Commande non trouvée' }); } const updateData = { statut }; if (statut === 'servie' && !commande.date_service) { updateData.date_service = new Date(); } await commande.update(updateData); res.status(200).json({ success: true, message: 'Statut de la commande mis à jour avec succès', data: commande }); } catch (error) { console.error('Error in updateStatut:', error); res.status(400).json({ success: false, message: 'Erreur lors de la mise à jour du statut', error: error.message }); } }; // Get commandes by status with menu details const getCommandesByStatut = async (req, res) => { try { const { statut } = req.params; const commandes = await Commande.findAll({ where: { statut }, order: [['date_commande', 'DESC']] }); // Fetch items with menu details for each commande if (commandes.length > 0) { const commandeIds = commandes.map(c => c.id); const itemsWithMenus = await sequelize.query(` SELECT ci.*, m.nom as menu_nom, m.commentaire as menu_description, m.prix as menu_prix_actuel FROM commande_items ci LEFT JOIN menus m ON ci.menu_id = m.id WHERE ci.commande_id IN (${commandeIds.join(',')}) ORDER BY ci.id `, { type: sequelize.QueryTypes.SELECT }); const itemsByCommandeId = itemsWithMenus.reduce((acc, item) => { if (!acc[item.commande_id]) { acc[item.commande_id] = []; } acc[item.commande_id].push(item); return acc; }, {}); commandes.forEach(commande => { commande.dataValues.items = itemsByCommandeId[commande.id] || []; }); } res.status(200).json({ success: true, data: commandes }); } catch (error) { console.error('Error in getCommandesByStatut:', error); res.status(500).json({ success: false, message: 'Erreur lors de la récupération des commandes par statut', error: error.message }); } }; // Get daily statistics const getDailyStats = async (req, res) => { try { const date = req.query.date || new Date().toISOString().split('T')[0]; const startOfDay = new Date(date + 'T00:00:00.000Z'); const endOfDay = new Date(date + 'T23:59:59.999Z'); const stats = await Commande.findAll({ where: { date_commande: { [Op.between]: [startOfDay, endOfDay] } }, attributes: [ 'statut', [sequelize.fn('COUNT', sequelize.col('id')), 'count'], [sequelize.fn('SUM', sequelize.col('total_ttc')), 'total_amount'] ], group: ['statut'], raw: true }); res.status(200).json({ success: true, data: { date, statistics: stats } }); } catch (error) { console.error('Error in getDailyStats:', error); res.status(500).json({ success: false, message: 'Erreur lors de la récupération des statistiques', error: error.message }); } }; module.exports = { getAllCommandes, getCommandeById, createCommande, updateCommande, deleteCommande, updateStatut, getCommandesByStatut, getDailyStats };