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.
760 lines
27 KiB
760 lines
27 KiB
const { Ticket, TicketItem, Commande, CommandeItem, Client, Utilisateur, Menu, sequelize } = require('../models/associations');
|
|
const { Op } = require('sequelize');
|
|
const PDFDocument = require('pdfkit');
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
|
|
class TicketController {
|
|
// Générer un numéro de ticket unique
|
|
async generateTicketNumber() {
|
|
const date = new Date();
|
|
const year = date.getFullYear().toString().substr(-2);
|
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
const day = date.getDate().toString().padStart(2, '0');
|
|
|
|
const prefix = `T${year}${month}${day}`;
|
|
|
|
// Trouver le dernier ticket du jour
|
|
const lastTicket = await Ticket.findOne({
|
|
where: {
|
|
numero_ticket: {
|
|
[Op.like]: `${prefix}%`
|
|
}
|
|
},
|
|
order: [['numero_ticket', 'DESC']]
|
|
});
|
|
|
|
let sequence = 1;
|
|
if (lastTicket) {
|
|
const lastSequence = parseInt(lastTicket.numero_ticket.substr(-4));
|
|
sequence = lastSequence + 1;
|
|
}
|
|
|
|
return `${prefix}${sequence.toString().padStart(4, '0')}`;
|
|
}
|
|
|
|
// Calculer les montants avec TVA
|
|
calculateAmounts(items, taux_tva = 20, remise = 0) {
|
|
let montant_ht = 0;
|
|
|
|
items.forEach(item => {
|
|
const prix_unitaire_ht = item.prix_unitaire_ttc / (1 + taux_tva / 100);
|
|
const montant_item_ht = prix_unitaire_ht * item.quantite - (item.remise_unitaire || 0);
|
|
montant_ht += montant_item_ht;
|
|
});
|
|
|
|
montant_ht -= remise;
|
|
const montant_tva = montant_ht * (taux_tva / 100);
|
|
const montant_ttc = montant_ht + montant_tva;
|
|
|
|
return {
|
|
montant_ht: Math.max(0, montant_ht),
|
|
montant_tva: Math.max(0, montant_tva),
|
|
montant_ttc: Math.max(0, montant_ttc)
|
|
};
|
|
}
|
|
|
|
// Obtenir tous les tickets avec pagination et filtres
|
|
async getAllTickets(req, res) {
|
|
try {
|
|
const {
|
|
page = 1,
|
|
limit = 10,
|
|
search = '',
|
|
statut,
|
|
mode_paiement,
|
|
date_debut,
|
|
date_fin,
|
|
client_id,
|
|
utilisateur_id,
|
|
sort_by = 'date_emission',
|
|
sort_order = 'DESC'
|
|
} = req.query;
|
|
|
|
const offset = (parseInt(page) - 1) * parseInt(limit);
|
|
|
|
const whereConditions = {};
|
|
|
|
if (search) {
|
|
whereConditions[Op.or] = [
|
|
{ numero_ticket: { [Op.like]: `%${search}%` } },
|
|
{ '$Client.nom$': { [Op.like]: `%${search}%` } },
|
|
{ '$Client.prenom$': { [Op.like]: `%${search}%` } }
|
|
];
|
|
}
|
|
|
|
if (statut) whereConditions.statut = statut;
|
|
if (mode_paiement) whereConditions.mode_paiement = mode_paiement;
|
|
if (client_id) whereConditions.client_id = client_id;
|
|
if (utilisateur_id) whereConditions.utilisateur_id = utilisateur_id;
|
|
|
|
if (date_debut || date_fin) {
|
|
whereConditions.date_emission = {};
|
|
if (date_debut) whereConditions.date_emission[Op.gte] = new Date(date_debut);
|
|
if (date_fin) whereConditions.date_emission[Op.lte] = new Date(date_fin);
|
|
}
|
|
|
|
const validSortFields = ['numero_ticket', 'date_emission', 'montant_ttc', 'statut'];
|
|
const sortField = validSortFields.includes(sort_by) ? sort_by : 'date_emission';
|
|
const sortOrder = ['ASC', 'DESC'].includes(sort_order.toUpperCase()) ?
|
|
sort_order.toUpperCase() : 'DESC';
|
|
|
|
const { count, rows } = await Ticket.findAndCountAll({
|
|
where: whereConditions,
|
|
include: [
|
|
{
|
|
model: Client,
|
|
attributes: ['id', 'nom', 'prenom', 'email', 'telephone'],
|
|
required: false
|
|
},
|
|
{
|
|
model: Utilisateur,
|
|
attributes: ['id', 'nom', 'prenom'],
|
|
required: true
|
|
},
|
|
{
|
|
model: Commande,
|
|
attributes: ['id', 'numero_commande'],
|
|
required: true
|
|
},
|
|
{
|
|
model: TicketItem,
|
|
attributes: ['id', 'nom_item', 'quantite', 'montant_ttc'],
|
|
required: false
|
|
}
|
|
],
|
|
order: [[sortField, sortOrder]],
|
|
limit: parseInt(limit),
|
|
offset: offset,
|
|
distinct: true
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
tickets: rows,
|
|
pagination: {
|
|
currentPage: parseInt(page),
|
|
totalPages: Math.ceil(count / parseInt(limit)),
|
|
totalItems: count,
|
|
itemsPerPage: parseInt(limit)
|
|
}
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Erreur lors de la récupération des tickets',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Obtenir un ticket par ID
|
|
async getTicketById(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const ticket = await Ticket.findByPk(id, {
|
|
include: [
|
|
{
|
|
model: Client,
|
|
required: false
|
|
},
|
|
{
|
|
model: Utilisateur,
|
|
attributes: ['id', 'nom', 'prenom', 'email']
|
|
},
|
|
{
|
|
model: Commande,
|
|
include: [{
|
|
model: CommandeItem,
|
|
include: [{ model: Menu, attributes: ['nom', 'description'] }]
|
|
}]
|
|
},
|
|
{
|
|
model: TicketItem,
|
|
required: false
|
|
}
|
|
]
|
|
});
|
|
|
|
if (!ticket) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Ticket non trouvé'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
data: ticket
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Erreur lors de la récupération du ticket',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Créer un ticket depuis une commande
|
|
async createTicketFromOrder(req, res) {
|
|
const transaction = await sequelize.transaction();
|
|
|
|
try {
|
|
const {
|
|
commande_id,
|
|
client_id,
|
|
utilisateur_id,
|
|
mode_paiement = 'especes',
|
|
taux_tva = 20,
|
|
remise = 0,
|
|
notes
|
|
} = req.body;
|
|
|
|
// Vérifier que la commande existe
|
|
const commande = await Commande.findByPk(commande_id, {
|
|
include: [{
|
|
model: CommandeItem,
|
|
include: [{ model: Menu }]
|
|
}],
|
|
transaction
|
|
});
|
|
|
|
if (!commande) {
|
|
await transaction.rollback();
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Commande non trouvée'
|
|
});
|
|
}
|
|
|
|
if (commande.CommandeItems.length === 0) {
|
|
await transaction.rollback();
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'La commande ne contient aucun item'
|
|
});
|
|
}
|
|
|
|
// Générer le numéro de ticket
|
|
const numero_ticket = await this.generateTicketNumber();
|
|
|
|
// Calculer les montants
|
|
const amounts = this.calculateAmounts(
|
|
commande.CommandeItems.map(item => ({
|
|
prix_unitaire_ttc: parseFloat(item.prix_unitaire),
|
|
quantite: item.quantite,
|
|
remise_unitaire: 0
|
|
})),
|
|
taux_tva,
|
|
remise
|
|
);
|
|
|
|
// Récupérer les données client si fourni
|
|
let donnees_client = null;
|
|
if (client_id) {
|
|
const client = await Client.findByPk(client_id, { transaction });
|
|
if (client) {
|
|
donnees_client = {
|
|
nom: client.nom,
|
|
prenom: client.prenom,
|
|
email: client.email,
|
|
telephone: client.telephone,
|
|
adresse: client.adresse
|
|
};
|
|
}
|
|
}
|
|
|
|
// Créer le ticket
|
|
const ticket = await Ticket.create({
|
|
numero_ticket,
|
|
commande_id,
|
|
client_id,
|
|
utilisateur_id,
|
|
montant_ht: amounts.montant_ht,
|
|
montant_tva: amounts.montant_tva,
|
|
montant_ttc: amounts.montant_ttc,
|
|
remise,
|
|
taux_tva,
|
|
mode_paiement,
|
|
statut: 'emis',
|
|
date_emission: new Date(),
|
|
notes,
|
|
donnees_client
|
|
}, { transaction });
|
|
|
|
// Créer les items du ticket
|
|
const ticketItems = await Promise.all(
|
|
commande.CommandeItems.map(async (item) => {
|
|
const prix_unitaire_ht = parseFloat(item.prix_unitaire) / (1 + taux_tva / 100);
|
|
const montant_ht = prix_unitaire_ht * item.quantite;
|
|
const montant_tva = montant_ht * (taux_tva / 100);
|
|
const montant_ttc = montant_ht + montant_tva;
|
|
|
|
return TicketItem.create({
|
|
ticket_id: ticket.id,
|
|
commande_item_id: item.id,
|
|
nom_item: item.Menu ? item.Menu.nom : `Item ${item.id}`,
|
|
description: item.notes,
|
|
quantite: item.quantite,
|
|
prix_unitaire_ht,
|
|
prix_unitaire_ttc: parseFloat(item.prix_unitaire),
|
|
montant_ht,
|
|
montant_tva,
|
|
montant_ttc,
|
|
taux_tva,
|
|
remise_unitaire: 0
|
|
}, { transaction });
|
|
})
|
|
);
|
|
|
|
await transaction.commit();
|
|
|
|
// Récupérer le ticket complet
|
|
const ticketComplet = await Ticket.findByPk(ticket.id, {
|
|
include: [
|
|
{ model: Client },
|
|
{ model: Utilisateur, attributes: ['nom', 'prenom'] },
|
|
{ model: Commande },
|
|
{ model: TicketItem }
|
|
]
|
|
});
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Ticket créé avec succès',
|
|
data: ticketComplet
|
|
});
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Erreur lors de la création du ticket',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Mettre à jour le statut d'un ticket
|
|
async updateTicketStatus(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { statut, date_paiement, notes } = req.body;
|
|
|
|
const ticket = await Ticket.findByPk(id);
|
|
if (!ticket) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Ticket non trouvé'
|
|
});
|
|
}
|
|
|
|
const updateData = { statut };
|
|
|
|
if (statut === 'paye' && date_paiement) {
|
|
updateData.date_paiement = new Date(date_paiement);
|
|
}
|
|
|
|
if (notes !== undefined) {
|
|
updateData.notes = notes;
|
|
}
|
|
|
|
await ticket.update(updateData);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Statut du ticket mis à jour avec succès',
|
|
data: ticket
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Erreur lors de la mise à jour du statut',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Obtenir les statistiques des tickets
|
|
async getTicketStats(req, res) {
|
|
try {
|
|
const { date_debut, date_fin } = req.query;
|
|
|
|
const whereConditions = {};
|
|
if (date_debut || date_fin) {
|
|
whereConditions.date_emission = {};
|
|
if (date_debut) whereConditions.date_emission[Op.gte] = new Date(date_debut);
|
|
if (date_fin) whereConditions.date_emission[Op.lte] = new Date(date_fin);
|
|
}
|
|
|
|
const [
|
|
total,
|
|
emis,
|
|
payes,
|
|
annules,
|
|
totalRevenue,
|
|
payedRevenue
|
|
] = await Promise.all([
|
|
Ticket.count({ where: whereConditions }),
|
|
Ticket.count({ where: { ...whereConditions, statut: 'emis' } }),
|
|
Ticket.count({ where: { ...whereConditions, statut: 'paye' } }),
|
|
Ticket.count({ where: { ...whereConditions, statut: 'annule' } }),
|
|
Ticket.sum('montant_ttc', { where: whereConditions }),
|
|
Ticket.sum('montant_ttc', {
|
|
where: { ...whereConditions, statut: 'paye' }
|
|
})
|
|
]);
|
|
|
|
// Statistiques par mode de paiement
|
|
const paymentStats = await Ticket.findAll({
|
|
attributes: [
|
|
'mode_paiement',
|
|
[sequelize.fn('COUNT', sequelize.col('id')), 'count'],
|
|
[sequelize.fn('SUM', sequelize.col('montant_ttc')), 'total']
|
|
],
|
|
where: { ...whereConditions, statut: 'paye' },
|
|
group: ['mode_paiement']
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
total,
|
|
emis,
|
|
payes,
|
|
annules,
|
|
totalRevenue: parseFloat(totalRevenue || 0),
|
|
payedRevenue: parseFloat(payedRevenue || 0),
|
|
paymentMethods: paymentStats.map(stat => ({
|
|
mode: stat.mode_paiement,
|
|
count: parseInt(stat.dataValues.count),
|
|
total: parseFloat(stat.dataValues.total || 0)
|
|
}))
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Erreur lors de la récupération des statistiques',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Générer un PDF pour un ticket
|
|
async generatePDF(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const ticket = await Ticket.findByPk(id, {
|
|
include: [
|
|
{ model: Client },
|
|
{ model: Utilisateur, attributes: ['nom', 'prenom'] },
|
|
{ model: TicketItem }
|
|
]
|
|
});
|
|
|
|
if (!ticket) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Ticket non trouvé'
|
|
});
|
|
}
|
|
|
|
// Créer le dossier s'il n'existe pas
|
|
const pdfDir = path.join(__dirname, '../uploads/tickets');
|
|
await fs.mkdir(pdfDir, { recursive: true });
|
|
|
|
const pdfPath = path.join(pdfDir, `ticket_${ticket.numero_ticket}.pdf`);
|
|
|
|
// Créer le document PDF
|
|
const doc = new PDFDocument();
|
|
doc.pipe(fs.createWriteStream(pdfPath));
|
|
|
|
// En-tête
|
|
doc.fontSize(20).text('TICKET DE CAISSE', { align: 'center' });
|
|
doc.moveDown();
|
|
|
|
doc.fontSize(12).text(`Numéro: ${ticket.numero_ticket}`, { align: 'left' });
|
|
doc.text(`Date: ${ticket.date_emission.toLocaleDateString('fr-FR')}`, { align: 'left' });
|
|
doc.text(`Serveur: ${ticket.Utilisateur.nom} ${ticket.Utilisateur.prenom}`, { align: 'left' });
|
|
|
|
if (ticket.Client) {
|
|
doc.text(`Client: ${ticket.Client.nom} ${ticket.Client.prenom}`, { align: 'left' });
|
|
}
|
|
|
|
doc.moveDown();
|
|
|
|
// Détail des items
|
|
doc.text('DÉTAIL:', { underline: true });
|
|
doc.moveDown(0.5);
|
|
|
|
ticket.TicketItems.forEach(item => {
|
|
doc.text(`${item.nom_item} x${item.quantite}`, { continued: true });
|
|
doc.text(`${parseFloat(item.montant_ttc).toFixed(2)}€`, { align: 'right' });
|
|
});
|
|
|
|
doc.moveDown();
|
|
|
|
// Totaux
|
|
doc.text(`Montant HT: ${parseFloat(ticket.montant_ht).toFixed(2)}€`, { align: 'right' });
|
|
doc.text(`TVA (${ticket.taux_tva}%): ${parseFloat(ticket.montant_tva).toFixed(2)}€`, { align: 'right' });
|
|
if (ticket.remise > 0) {
|
|
doc.text(`Remise: ${parseFloat(ticket.remise).toFixed(2)}€`, { align: 'right' });
|
|
}
|
|
doc.fontSize(14).text(`TOTAL TTC: ${parseFloat(ticket.montant_ttc).toFixed(2)}€`, { align: 'right' });
|
|
|
|
doc.moveDown();
|
|
doc.fontSize(12).text(`Mode de paiement: ${ticket.mode_paiement.toUpperCase()}`, { align: 'left' });
|
|
doc.text(`Statut: ${ticket.statut.toUpperCase()}`, { align: 'left' });
|
|
|
|
if (ticket.notes) {
|
|
doc.moveDown();
|
|
doc.text(`Notes: ${ticket.notes}`, { align: 'left' });
|
|
}
|
|
|
|
// Pied de page
|
|
doc.moveDown(2);
|
|
doc.fontSize(10).text('Merci de votre visite !', { align: 'center' });
|
|
doc.text('À bientôt dans notre restaurant', { align: 'center' });
|
|
|
|
doc.end();
|
|
|
|
// Mettre à jour le chemin du PDF dans la base
|
|
await ticket.update({ facture_pdf: `tickets/ticket_${ticket.numero_ticket}.pdf` });
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'PDF généré avec succès',
|
|
data: {
|
|
pdf_path: `/uploads/tickets/ticket_${ticket.numero_ticket}.pdf`,
|
|
ticket_id: ticket.id
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Erreur lors de la génération du PDF',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Supprimer un ticket
|
|
async deleteTicket(req, res) {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const ticket = await Ticket.findByPk(id);
|
|
if (!ticket) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Ticket non trouvé'
|
|
});
|
|
}
|
|
|
|
// Vérifier si le ticket peut être supprimé
|
|
if (ticket.statut === 'paye') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Impossible de supprimer un ticket payé. Vous pouvez l\'annuler.'
|
|
});
|
|
}
|
|
|
|
// Supprimer le fichier PDF s'il existe
|
|
if (ticket.facture_pdf) {
|
|
const pdfPath = path.join(__dirname, '../uploads/', ticket.facture_pdf);
|
|
try {
|
|
await fs.unlink(pdfPath);
|
|
} catch (err) {
|
|
console.log('PDF file not found or already deleted');
|
|
}
|
|
}
|
|
|
|
await ticket.destroy();
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Ticket supprimé avec succès'
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Erreur lors de la suppression du ticket',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Dupliquer un ticket
|
|
async duplicateTicket(req, res) {
|
|
const transaction = await sequelize.transaction();
|
|
|
|
try {
|
|
const { id } = req.params;
|
|
const { utilisateur_id, notes } = req.body;
|
|
|
|
const originalTicket = await Ticket.findByPk(id, {
|
|
include: [{ model: TicketItem }],
|
|
transaction
|
|
});
|
|
|
|
if (!originalTicket) {
|
|
await transaction.rollback();
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Ticket original non trouvé'
|
|
});
|
|
}
|
|
|
|
// Générer un nouveau numéro de ticket
|
|
const numero_ticket = await this.generateTicketNumber();
|
|
|
|
// Créer le nouveau ticket
|
|
const newTicket = await Ticket.create({
|
|
numero_ticket,
|
|
commande_id: originalTicket.commande_id,
|
|
client_id: originalTicket.client_id,
|
|
utilisateur_id: utilisateur_id || originalTicket.utilisateur_id,
|
|
montant_ht: originalTicket.montant_ht,
|
|
montant_tva: originalTicket.montant_tva,
|
|
montant_ttc: originalTicket.montant_ttc,
|
|
remise: originalTicket.remise,
|
|
taux_tva: originalTicket.taux_tva,
|
|
mode_paiement: originalTicket.mode_paiement,
|
|
statut: 'brouillon',
|
|
date_emission: new Date(),
|
|
notes: notes || `Copie du ticket ${originalTicket.numero_ticket}`,
|
|
donnees_client: originalTicket.donnees_client
|
|
}, { transaction });
|
|
|
|
// Dupliquer les items
|
|
const newItems = await Promise.all(
|
|
originalTicket.TicketItems.map(item =>
|
|
TicketItem.create({
|
|
ticket_id: newTicket.id,
|
|
commande_item_id: item.commande_item_id,
|
|
nom_item: item.nom_item,
|
|
description: item.description,
|
|
quantite: item.quantite,
|
|
prix_unitaire_ht: item.prix_unitaire_ht,
|
|
prix_unitaire_ttc: item.prix_unitaire_ttc,
|
|
montant_ht: item.montant_ht,
|
|
montant_tva: item.montant_tva,
|
|
montant_ttc: item.montant_ttc,
|
|
taux_tva: item.taux_tva,
|
|
remise_unitaire: item.remise_unitaire
|
|
}, { transaction })
|
|
)
|
|
);
|
|
|
|
await transaction.commit();
|
|
|
|
// Récupérer le ticket complet
|
|
const ticketComplet = await Ticket.findByPk(newTicket.id, {
|
|
include: [
|
|
{ model: Client },
|
|
{ model: Utilisateur, attributes: ['nom', 'prenom'] },
|
|
{ model: TicketItem }
|
|
]
|
|
});
|
|
|
|
res.status(201).json({
|
|
success: true,
|
|
message: 'Ticket dupliqué avec succès',
|
|
data: ticketComplet
|
|
});
|
|
} catch (error) {
|
|
await transaction.rollback();
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Erreur lors de la duplication du ticket',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Recherche avancée de tickets
|
|
async searchTickets(req, res) {
|
|
try {
|
|
const {
|
|
numero_ticket,
|
|
client_nom,
|
|
montant_min,
|
|
montant_max,
|
|
date_debut,
|
|
date_fin,
|
|
statut,
|
|
mode_paiement,
|
|
limit = 50
|
|
} = req.query;
|
|
|
|
const whereConditions = {};
|
|
const clientWhereConditions = {};
|
|
|
|
if (numero_ticket) {
|
|
whereConditions.numero_ticket = { [Op.like]: `%${numero_ticket}%` };
|
|
}
|
|
|
|
if (client_nom) {
|
|
clientWhereConditions[Op.or] = [
|
|
{ nom: { [Op.like]: `%${client_nom}%` } },
|
|
{ prenom: { [Op.like]: `%${client_nom}%` } }
|
|
];
|
|
}
|
|
|
|
if (montant_min || montant_max) {
|
|
whereConditions.montant_ttc = {};
|
|
if (montant_min) whereConditions.montant_ttc[Op.gte] = parseFloat(montant_min);
|
|
if (montant_max) whereConditions.montant_ttc[Op.lte] = parseFloat(montant_max);
|
|
}
|
|
|
|
if (date_debut || date_fin) {
|
|
whereConditions.date_emission = {};
|
|
if (date_debut) whereConditions.date_emission[Op.gte] = new Date(date_debut);
|
|
if (date_fin) whereConditions.date_emission[Op.lte] = new Date(date_fin);
|
|
}
|
|
|
|
if (statut) whereConditions.statut = statut;
|
|
if (mode_paiement) whereConditions.mode_paiement = mode_paiement;
|
|
|
|
const tickets = await Ticket.findAll({
|
|
where: whereConditions,
|
|
include: [
|
|
{
|
|
model: Client,
|
|
where: Object.keys(clientWhereConditions).length > 0 ? clientWhereConditions : undefined,
|
|
required: false,
|
|
attributes: ['id', 'nom', 'prenom', 'email']
|
|
},
|
|
{
|
|
model: Utilisateur,
|
|
attributes: ['id', 'nom', 'prenom']
|
|
}
|
|
],
|
|
order: [['date_emission', 'DESC']],
|
|
limit: parseInt(limit)
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
tickets,
|
|
count: tickets.length
|
|
}
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Erreur lors de la recherche',
|
|
error: error.message
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new TicketController();
|
|
|
|
|