const bcrypt = require("bcrypt"); const prisma = require("../config/prisma"); const { getGameWindow, expireOverdueClaims } = require("../utils/gameWindow"); const AGE_BRACKETS = ["-18", "18-24", "25-34", "35-44", "45-54", "55-64", "65+"]; function getAgeBracket(birthdate) { if (!birthdate) return "unknown"; const now = new Date(); let age = now.getFullYear() - birthdate.getFullYear(); const hasHadBirthdayThisYear = now.getMonth() > birthdate.getMonth() || (now.getMonth() === birthdate.getMonth() && now.getDate() >= birthdate.getDate()); if (!hasHadBirthdayThisYear) age -= 1; if (age < 18) return "-18"; if (age <= 24) return "18-24"; if (age <= 34) return "25-34"; if (age <= 44) return "35-44"; if (age <= 54) return "45-54"; if (age <= 64) return "55-64"; return "65+"; } function computeAgeStats(users) { const counts = Object.fromEntries([...AGE_BRACKETS, "unknown"].map((b) => [b, 0])); for (const user of users) { counts[getAgeBracket(user.birthdate)] += 1; } return counts; } const getStats = async (req, res) => { try { await expireOverdueClaims(); const gameWindow = await getGameWindow(); const [ ticketsTotal, ticketsUsed, participations, claimsPending, claimsDelivered, claimsCancelled, users, men, women, clientsWithBirthdate, ] = await Promise.all([ prisma.ticket.count(), prisma.ticket.count({ where: { isUsed: true } }), prisma.participation.count(), prisma.claim.count({ where: { status: "PENDING" } }), prisma.claim.count({ where: { status: "DELIVERED" } }), prisma.claim.count({ where: { status: "CANCELLED" } }), prisma.user.count({ where: { role: "CLIENT" } }), prisma.user.count({ where: { role: "CLIENT", gender: "Homme" } }), prisma.user.count({ where: { role: "CLIENT", gender: "Femme" } }), prisma.user.findMany({ where: { role: "CLIENT" }, select: { birthdate: true } }), ]); const lotsStats = await prisma.lot.findMany({ select: { id: true, name: true, percentage: true, _count: { select: { tickets: true, participations: true, }, }, }, }); return res.json({ ticketsTotal, ticketsUsed, ticketsRemaining: ticketsTotal - ticketsUsed, participations, claimsPending, claimsDelivered, claimsCancelled, users, gameWindow, ageStats: computeAgeStats(clientsWithBirthdate), genderStats: { men, women, other: users - men - women, }, lotsStats, }); } catch (error) { console.error("Erreur stats admin:", error); return res.status(500).json({ message: "Erreur serveur lors de la récupération des statistiques.", }); } }; const getUsers = async (req, res) => { try { const users = await prisma.user.findMany({ where: { role: "CLIENT" }, select: { id: true, firstname: true, lastname: true, email: true, gender: true, birthdate: true, marketingOptIn: true, createdAt: true, _count: { select: { participations: true, }, }, }, orderBy: { createdAt: "desc", }, }); return res.json({ users }); } catch (error) { console.error("Erreur liste utilisateurs:", error); return res.status(500).json({ message: "Erreur serveur lors de la récupération des utilisateurs.", }); } }; const getEmailingContacts = async (req, res) => { try { const contacts = await prisma.user.findMany({ where: { role: "CLIENT", marketingOptIn: true, }, select: { firstname: true, lastname: true, email: true, createdAt: true, }, orderBy: { createdAt: "desc", }, }); return res.json({ total: contacts.length, contacts, }); } catch (error) { console.error("Erreur emailing:", error); return res.status(500).json({ message: "Erreur serveur lors de la récupération des contacts emailing.", }); } }; const createEmployee = async (req, res) => { try { const { firstname, lastname, email, password, birthdate, gender } = req.body; const existingUser = await prisma.user.findUnique({ where: { email }, }); if (existingUser) { return res.status(409).json({ message: "Un compte existe déjà avec cet email.", }); } const hashedPassword = await bcrypt.hash(password, 10); const employee = await prisma.user.create({ data: { firstname, lastname, email, password: hashedPassword, birthdate: birthdate ? new Date(birthdate) : null, gender, role: "EMPLOYEE", marketingOptIn: false, }, select: { id: true, firstname: true, lastname: true, email: true, role: true, createdAt: true, }, }); return res.status(201).json({ message: "Employé créé avec succès.", employee, }); } catch (error) { console.error("Erreur création employé:", error); return res.status(500).json({ message: "Erreur serveur lors de la création de l'employé.", }); } }; const drawWinner = async (req, res) => { try { const existingWinner = await prisma.grandPrizeWinner.findFirst({ include: { user: { select: { id: true, firstname: true, lastname: true, email: true, }, }, }, }); if (existingWinner) { return res.status(409).json({ message: "Le tirage au sort a déjà été effectué.", winner: existingWinner, }); } const participants = await prisma.user.findMany({ where: { role: "CLIENT", participations: { some: {}, }, }, select: { id: true, firstname: true, lastname: true, email: true, }, }); if (participants.length === 0) { return res.status(400).json({ message: "Aucun participant disponible pour le tirage.", }); } const randomIndex = Math.floor(Math.random() * participants.length); const winner = participants[randomIndex]; const savedWinner = await prisma.grandPrizeWinner.create({ data: { userId: winner.id, }, include: { user: { select: { id: true, firstname: true, lastname: true, email: true, }, }, }, }); return res.status(201).json({ message: "Tirage au sort effectué avec succès.", totalEligibleParticipants: participants.length, winner: savedWinner, }); } catch (error) { console.error("Erreur tirage au sort:", error); return res.status(500).json({ message: "Erreur serveur lors du tirage au sort.", }); } }; const getGrandPrizeWinner = async (req, res) => { try { const winner = await prisma.grandPrizeWinner.findFirst({ include: { user: { select: { id: true, firstname: true, lastname: true, email: true, }, }, }, }); if (!winner) { return res.status(404).json({ message: "Aucun gagnant du gros lot n'a encore été tiré.", }); } return res.json({ winner, }); } catch (error) { console.error("Erreur récupération gagnant:", error); return res.status(500).json({ message: "Erreur serveur lors de la récupération du gagnant.", }); } }; module.exports = { getStats, getUsers, getEmailingContacts, createEmployee, drawWinner, getGrandPrizeWinner, };