Added Participation Page

This commit is contained in:
2026-02-15 14:36:28 +01:00
parent da2e32d004
commit 5185a41bb6
23 changed files with 2643 additions and 67 deletions

View File

@@ -0,0 +1,57 @@
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { getCurrentUser } from '@/lib/auth';
import { generateParticipationPDF, getParticipationStoragePath } from '@/lib/participation-pdf';
// GET - Récupérer le PDF d'une participation (régénéré à chaque vue pour le design à jour)
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser();
if (!user) {
return new NextResponse('Non autorisé', { status: 401 });
}
const participation = await prisma.participationFinanciere.findUnique({
where: { id: params.id },
include: {
adherent: true,
trajet: true,
},
});
if (!participation) {
return new NextResponse('Participation non trouvée', { status: 404 });
}
const filePath = getParticipationStoragePath(participation.id);
const pdfBuffer = await generateParticipationPDF(
{
adherentNom: participation.adherent.nom,
adherentPrenom: participation.adherent.prenom,
adherentAdresse: participation.adherent.adresse,
destinataireEmail: participation.destinataireEmail,
destinataireNom: participation.destinataireNom,
dateTrajet: participation.trajet.date,
adresseDepart: participation.trajet.adresseDepart,
adresseArrivee: participation.trajet.adresseArrivee,
montant: participation.montant ?? undefined,
complement: participation.complement ?? undefined,
participationId: participation.id,
},
filePath
);
return new NextResponse(pdfBuffer, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `inline; filename="participation-${params.id}.pdf"`,
},
});
} catch (error) {
console.error('Erreur lors de la récupération du PDF:', error);
return new NextResponse('Erreur serveur', { status: 500 });
}
}

View File

@@ -0,0 +1,203 @@
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { getCurrentUser } from '@/lib/auth';
import { generateParticipationPDF, getParticipationStoragePath } from '@/lib/participation-pdf';
// GET - Récupérer une participation
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
}
const participation = await prisma.participationFinanciere.findUnique({
where: { id: params.id },
include: {
adherent: true,
trajet: {
include: {
chauffeur: { select: { nom: true, prenom: true } },
universPro: { select: { nom: true, prenom: true, nomEntreprise: true, email: true } },
},
},
},
});
if (!participation) {
return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 });
}
return NextResponse.json(participation);
} catch (error) {
console.error('Erreur lors de la récupération de la participation:', error);
return NextResponse.json(
{ error: 'Erreur serveur' },
{ status: 500 }
);
}
}
// PUT - Modifier une participation
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
}
const participation = await prisma.participationFinanciere.findUnique({
where: { id: params.id },
include: {
adherent: true,
trajet: { include: { adherent: true } },
},
});
if (!participation) {
return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 });
}
const body = await request.json();
const { destinataireEmail, destinataireNom, destinataireType, montant, complement } = body;
const updated = await prisma.participationFinanciere.update({
where: { id: params.id },
data: {
...(destinataireEmail && { destinataireEmail }),
...(destinataireNom && { destinataireNom }),
...(destinataireType && { destinataireType }),
...(montant !== undefined && { montant }),
...(complement !== undefined && { complement }),
},
});
// Régénérer le PDF si les données ont changé
const dataToUpdate =
destinataireEmail || destinataireNom || montant !== undefined || complement !== undefined;
if (dataToUpdate) {
const filePath = getParticipationStoragePath(participation.id);
await generateParticipationPDF(
{
adherentNom: participation.adherent.nom,
adherentPrenom: participation.adherent.prenom,
adherentAdresse: participation.adherent.adresse,
destinataireEmail: updated.destinataireEmail,
destinataireNom: updated.destinataireNom,
dateTrajet: participation.trajet.date,
adresseDepart: participation.trajet.adresseDepart,
adresseArrivee: participation.trajet.adresseArrivee,
montant: updated.montant ?? undefined,
complement: updated.complement ?? undefined,
participationId: participation.id,
},
filePath
);
await prisma.participationFinanciere.update({
where: { id: params.id },
data: { filePath },
});
}
const fullUpdated = await prisma.participationFinanciere.findUnique({
where: { id: params.id },
include: {
adherent: { select: { id: true, nom: true, prenom: true, email: true } },
trajet: { select: { id: true, date: true, adresseDepart: true, adresseArrivee: true, statut: true } },
},
});
return NextResponse.json(fullUpdated);
} catch (error) {
console.error('Erreur lors de la mise à jour de la participation:', error);
return NextResponse.json(
{ error: 'Erreur serveur' },
{ status: 500 }
);
}
}
// PATCH - Changer le statut d'une participation
export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
}
const body = await request.json();
const { statut } = body;
const validStatuts = ['en_attente', 'envoye', 'paye', 'archive'];
if (!statut || !validStatuts.includes(statut)) {
return NextResponse.json({ error: 'Statut invalide' }, { status: 400 });
}
const participation = await prisma.participationFinanciere.findUnique({
where: { id: params.id },
});
if (!participation) {
return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 });
}
const updated = await prisma.participationFinanciere.update({
where: { id: params.id },
data: { statut },
});
return NextResponse.json(updated);
} catch (error) {
console.error('Erreur lors du changement de statut:', error);
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
}
}
// DELETE - Supprimer une participation
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
}
const participation = await prisma.participationFinanciere.findUnique({
where: { id: params.id },
});
if (!participation) {
return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 });
}
const fs = await import('fs');
const path = await import('path');
const filePath = getParticipationStoragePath(participation.id);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
await prisma.participationFinanciere.delete({
where: { id: params.id },
});
return NextResponse.json({ message: 'Participation supprimée' });
} catch (error) {
console.error('Erreur lors de la suppression de la participation:', error);
return NextResponse.json(
{ error: 'Erreur serveur' },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,110 @@
import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { getCurrentUser } from '@/lib/auth';
import { getParticipationStoragePath } from '@/lib/participation-pdf';
import fs from 'fs';
// POST - Envoyer la participation par email
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser();
if (!user) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
}
const participation = await prisma.participationFinanciere.findUnique({
where: { id: params.id },
include: {
adherent: { select: { prenom: true, nom: true } },
trajet: { select: { date: true } },
},
});
if (!participation) {
return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 });
}
const filePath = getParticipationStoragePath(participation.id);
if (!fs.existsSync(filePath)) {
return NextResponse.json(
{ error: 'Le document PDF n\'a pas été généré' },
{ status: 400 }
);
}
// Vérifier la configuration email
const smtpHost = process.env.SMTP_HOST;
const smtpUser = process.env.SMTP_USER;
const smtpPass = process.env.SMTP_PASS;
if (!smtpHost || !smtpUser || !smtpPass) {
return NextResponse.json(
{
error:
"L'envoi par email n'est pas configuré. Configurez SMTP_HOST, SMTP_USER et SMTP_PASS dans les variables d'environnement.",
},
{ status: 503 }
);
}
const nodemailer = await import('nodemailer');
const transporter = nodemailer.default.createTransport({
host: smtpHost,
port: parseInt(process.env.SMTP_PORT || '587'),
secure: process.env.SMTP_SECURE === 'true',
auth: {
user: smtpUser,
pass: smtpPass,
},
});
const pdfBuffer = fs.readFileSync(filePath);
const dateTrajet = participation.trajet?.date
? new Date(participation.trajet.date).toLocaleDateString('fr-FR', {
day: '2-digit',
month: 'long',
year: 'numeric',
})
: '';
await transporter.sendMail({
from: process.env.SMTP_FROM || smtpUser,
to: participation.destinataireEmail,
subject: `Participation financière - ${participation.adherent.prenom} ${participation.adherent.nom} - ${dateTrajet}`,
text: `Bonjour,\n\nVeuillez trouver ci-joint la participation financière concernant le trajet du ${dateTrajet} pour ${participation.adherent.prenom} ${participation.adherent.nom}.\n\nCordialement`,
html: `
<p>Bonjour,</p>
<p>Veuillez trouver ci-joint la participation financière concernant le trajet du <strong>${dateTrajet}</strong> pour <strong>${participation.adherent.prenom} ${participation.adherent.nom}</strong>.</p>
<p>Cordialement</p>
`,
attachments: [
{
filename: `participation-financiere-${params.id}.pdf`,
content: pdfBuffer,
},
],
});
// Mettre à jour le statut en "envoyé"
await prisma.participationFinanciere.update({
where: { id: params.id },
data: { statut: 'envoye' },
});
return NextResponse.json({
message: `Participation envoyée à ${participation.destinataireEmail}`,
});
} catch (error) {
console.error('Erreur lors de l\'envoi de l\'email:', error);
return NextResponse.json(
{
error:
error instanceof Error ? error.message : 'Erreur lors de l\'envoi de l\'email',
},
{ status: 500 }
);
}
}