/** * Controller employé */ import { pool } from '../../db.js'; import { AppError, asyncHandler } from '../middleware/errorHandler.js'; /** * Récupérer les tickets en attente de validation * GET /api/employee/pending-tickets */ export const getPendingTickets = asyncHandler(async (req, res) => { const { page = 1, limit = 20 } = req.query; const offset = (page - 1) * limit; const result = await pool.query( `SELECT t.id, t.code, t.status, t.played_at, u.id as user_id, u.email as user_email, u.first_name as user_first_name, u.last_name as user_last_name, u.phone as user_phone, p.id as prize_id, p.name as prize_name, p.type as prize_type, p.value as prize_value, p.description as prize_description FROM tickets t JOIN users u ON t.user_id = u.id JOIN prizes p ON t.prize_id = p.id WHERE t.status = 'PENDING' ORDER BY t.played_at ASC LIMIT $1 OFFSET $2`, [limit, offset] ); // Compter le total const countResult = await pool.query( 'SELECT COUNT(*) FROM tickets WHERE status = $1', ['PENDING'] ); const total = parseInt(countResult.rows[0].count); // Transform data to match frontend expectations const transformedData = result.rows.map(row => ({ id: row.id, code: row.code, status: row.status, playedAt: row.played_at, user: { id: row.user_id, email: row.user_email, firstName: row.user_first_name, lastName: row.user_last_name, phone: row.user_phone, }, prize: { id: row.prize_id, name: row.prize_name, type: row.prize_type, value: row.prize_value, description: row.prize_description, }, })); res.json({ success: true, data: transformedData, pagination: { total, page: parseInt(page), limit: parseInt(limit), totalPages: Math.ceil(total / limit), }, }); }); /** * Rechercher un ticket par code * GET /api/employee/search-ticket */ export const searchTicket = asyncHandler(async (req, res, next) => { const { code } = req.query; const result = await pool.query( `SELECT t.id, t.code, t.status, t.played_at, t.claimed_at, t.validated_at, t.rejection_reason, u.id as user_id, u.email as user_email, u.first_name || ' ' || u.last_name as user_name, u.phone as user_phone, p.id as prize_id, p.name as prize_name, p.type as prize_type, p.value as prize_value, p.description as prize_description, v.first_name || ' ' || v.last_name as validated_by_name FROM tickets t JOIN users u ON t.user_id = u.id JOIN prizes p ON t.prize_id = p.id LEFT JOIN users v ON t.validated_by = v.id WHERE t.code = $1`, [code] ); if (result.rows.length === 0) { return next(new AppError('Ticket non trouvé', 404)); } res.json({ success: true, data: result.rows[0], }); }); /** * Valider ou rejeter un ticket * POST /api/employee/validate-ticket */ export const validateTicket = asyncHandler(async (req, res, next) => { const { ticketId, action, rejectionReason } = req.body; const employeeId = req.user.id; // Récupérer le ticket const ticketResult = await pool.query( 'SELECT id, status, user_id FROM tickets WHERE id = $1', [ticketId] ); if (ticketResult.rows.length === 0) { return next(new AppError('Ticket non trouvé', 404)); } const ticket = ticketResult.rows[0]; if (ticket.status !== 'PENDING') { return next(new AppError('Ce ticket a déjà été traité', 400)); } const client = await pool.connect(); try { await client.query('BEGIN'); if (action === 'APPROVE') { // Approuver le ticket await client.query( `UPDATE tickets SET status = 'CLAIMED', validated_by = $1, validated_at = CURRENT_TIMESTAMP, claimed_at = CURRENT_TIMESTAMP WHERE id = $2`, [employeeId, ticketId] ); await client.query('COMMIT'); res.json({ success: true, message: 'Ticket validé avec succès', }); } else if (action === 'REJECT') { // Rejeter le ticket await client.query( `UPDATE tickets SET status = 'REJECTED', validated_by = $1, validated_at = CURRENT_TIMESTAMP, rejection_reason = $2 WHERE id = $3`, [employeeId, rejectionReason, ticketId] ); // Incrémenter le stock du prix (le prix est remis en circulation) await client.query( 'UPDATE prizes SET stock = stock + 1 WHERE id = (SELECT prize_id FROM tickets WHERE id = $1)', [ticketId] ); await client.query('COMMIT'); res.json({ success: true, message: 'Ticket rejeté', }); } } catch (error) { await client.query('ROLLBACK'); throw error; } finally { client.release(); } }); /** * Statistiques pour l'employé * GET /api/employee/stats */ export const getEmployeeStats = asyncHandler(async (req, res) => { const employeeId = req.user.id; const result = await pool.query( `SELECT COUNT(*) as total_validated, COUNT(CASE WHEN status = 'CLAIMED' THEN 1 END) as total_approved, COUNT(CASE WHEN status = 'REJECTED' THEN 1 END) as total_rejected FROM tickets WHERE validated_by = $1`, [employeeId] ); // Statistiques pour aujourd'hui const todayResult = await pool.query( `SELECT COUNT(CASE WHEN status = 'CLAIMED' THEN 1 END) as claimed_today FROM tickets WHERE validated_by = $1 AND DATE(validated_at) = CURRENT_DATE`, [employeeId] ); const pendingResult = await pool.query( 'SELECT COUNT(*) as pending_count FROM tickets WHERE status = $1', ['PENDING'] ); res.json({ success: true, data: { ...result.rows[0], claimed_today: parseInt(todayResult.rows[0].claimed_today) || 0, pending_tickets: parseInt(pendingResult.rows[0].pending_count), }, }); }); /** * Rechercher tous les gains d'un client * GET /api/employee/client-prizes */ export const getClientPrizes = asyncHandler(async (req, res) => { const { email, phone } = req.query; if (!email && !phone) { throw new AppError('Email ou téléphone requis', 400); } // Chercher l'utilisateur let userQuery = 'SELECT id, email, first_name, last_name, phone FROM users WHERE role = $1'; const params = ['CLIENT']; if (email) { userQuery += ' AND email ILIKE $2'; params.push(`%${email}%`); } else if (phone) { userQuery += ' AND phone ILIKE $2'; params.push(`%${phone}%`); } const userResult = await pool.query(userQuery, params); if (userResult.rows.length === 0) { throw new AppError('Client non trouvé', 404); } const user = userResult.rows[0]; // Récupérer tous les tickets gagnants du client const ticketsResult = await pool.query( `SELECT t.id, t.code, t.status, t.played_at, t.claimed_at, t.validated_at, p.id as prize_id, p.name as prize_name, p.type as prize_type, p.value as prize_value, p.description as prize_description, v.first_name || ' ' || v.last_name as validated_by_name FROM tickets t JOIN prizes p ON t.prize_id = p.id LEFT JOIN users v ON t.validated_by = v.id WHERE t.user_id = $1 AND t.played_at IS NOT NULL ORDER BY t.played_at DESC`, [user.id] ); res.json({ success: true, data: { client: { id: user.id, email: user.email, firstName: user.first_name, lastName: user.last_name, phone: user.phone, }, prizes: ticketsResult.rows.map((t) => ({ ticketId: t.id, ticketCode: t.code, status: t.status, playedAt: t.played_at, claimedAt: t.claimed_at, validatedAt: t.validated_at, validatedBy: t.validated_by_name, prize: { id: t.prize_id, name: t.prize_name, type: t.prize_type, value: t.prize_value, description: t.prize_description, }, })), totalPrizes: ticketsResult.rows.length, pendingPrizes: ticketsResult.rows.filter((t) => t.status === 'PENDING').length, claimedPrizes: ticketsResult.rows.filter((t) => t.status === 'CLAIMED').length, }, }); }); /** * Historique des validations de l'employé * GET /api/employee/history */ export const getEmployeeHistory = asyncHandler(async (req, res) => { const employeeId = req.user.id; const result = await pool.query( `SELECT t.id, t.code, t.status, t.played_at, t.claimed_at, t.validated_at, t.rejection_reason, u.email as user_email, u.first_name || ' ' || u.last_name as user_name, p.name as prize_name, p.value as prize_value FROM tickets t JOIN users u ON t.user_id = u.id JOIN prizes p ON t.prize_id = p.id WHERE t.validated_by = $1 ORDER BY t.validated_at DESC`, [employeeId] ); res.json({ success: true, data: result.rows, }); }); export default { getPendingTickets, searchTicket, validateTicket, getEmployeeStats, getClientPrizes, getEmployeeHistory, };