Added Budget Page
This commit is contained in:
161
app/api/budget/route.ts
Normal file
161
app/api/budget/route.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { getCurrentUser } from '@/lib/auth';
|
||||
|
||||
// GET - Liste tous les prescripteurs avec budgets, consommé et restant
|
||||
// PATCH - Met à jour le budget d'un prescripteur (body: { prescripteurValue, montant })
|
||||
export async function GET() {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
||||
}
|
||||
|
||||
const [prescripteurs, budgets, participations] = await Promise.all([
|
||||
prisma.adherentOption.findMany({
|
||||
where: { type: 'prescripteur' },
|
||||
orderBy: [{ order: 'asc' }, { value: 'asc' }],
|
||||
}),
|
||||
prisma.prescripteurBudget.findMany(),
|
||||
prisma.participationFinanciere.findMany({
|
||||
include: {
|
||||
adherent: { select: { prescripteur: true, prenom: true, nom: true } },
|
||||
trajet: { select: { date: true } },
|
||||
},
|
||||
orderBy: { createdAt: 'desc' },
|
||||
}),
|
||||
]);
|
||||
|
||||
const consomméMap = new Map<string, number>();
|
||||
const historiqueMap = new Map<string, Array<{ id: string; date: string; adherentNom: string; montant: number }>>();
|
||||
|
||||
for (const p of participations) {
|
||||
const prescripteur = p.adherent.prescripteur;
|
||||
if (!prescripteur) continue;
|
||||
const montant = p.montant ?? 0;
|
||||
if (['envoye', 'paye'].includes(p.statut)) {
|
||||
consomméMap.set(prescripteur, (consomméMap.get(prescripteur) ?? 0) + montant);
|
||||
}
|
||||
const hist = historiqueMap.get(prescripteur) ?? [];
|
||||
hist.push({
|
||||
id: p.id,
|
||||
date: p.trajet.date.toISOString(),
|
||||
adherentNom: `${p.adherent.prenom} ${p.adherent.nom}`,
|
||||
montant,
|
||||
});
|
||||
historiqueMap.set(prescripteur, hist);
|
||||
}
|
||||
|
||||
const budgetMap = new Map(budgets.map((b) => [b.prescripteurValue, { montant: b.montant, ajustementConsomme: b.ajustementConsomme ?? 0 }]));
|
||||
|
||||
const items = prescripteurs.map((p) => {
|
||||
const budgetData = budgetMap.get(p.value);
|
||||
const montant = budgetData?.montant ?? 0;
|
||||
const ajustementConsomme = budgetData?.ajustementConsomme ?? 0;
|
||||
const consomméCalculé = consomméMap.get(p.value) ?? 0;
|
||||
const consommé = consomméCalculé + ajustementConsomme;
|
||||
const restant = Math.max(0, montant - consommé);
|
||||
const historique = (historiqueMap.get(p.value) ?? []).sort(
|
||||
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
|
||||
);
|
||||
return {
|
||||
id: p.id,
|
||||
value: p.value,
|
||||
montant,
|
||||
consommé,
|
||||
restant,
|
||||
ajustementConsomme,
|
||||
historique,
|
||||
};
|
||||
});
|
||||
|
||||
const totalAlloue = items.reduce((s, i) => s + i.montant, 0);
|
||||
const totalConsomme = items.reduce((s, i) => s + i.consommé, 0);
|
||||
const totalRestant = Math.max(0, totalAlloue - totalConsomme);
|
||||
|
||||
return NextResponse.json({
|
||||
global: { totalAlloue, totalConsomme, totalRestant },
|
||||
prescripteurs: items,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des budgets:', error);
|
||||
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: NextRequest) {
|
||||
try {
|
||||
const user = await getCurrentUser();
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
|
||||
}
|
||||
|
||||
const body = await request.json();
|
||||
const { prescripteurValue, montant: montantRaw, montantToAdd, rectifier, ajustementConsomme: ajRaw } = body;
|
||||
|
||||
if (!prescripteurValue || typeof prescripteurValue !== 'string') {
|
||||
return NextResponse.json({ error: 'Prescripteur requis' }, { status: 400 });
|
||||
}
|
||||
|
||||
const exists = await prisma.adherentOption.findFirst({
|
||||
where: { type: 'prescripteur', value: prescripteurValue },
|
||||
});
|
||||
|
||||
if (!exists) {
|
||||
return NextResponse.json({ error: 'Prescripteur non trouvé' }, { status: 404 });
|
||||
}
|
||||
|
||||
const existing = await prisma.prescripteurBudget.findUnique({
|
||||
where: { prescripteurValue },
|
||||
});
|
||||
|
||||
let montantFinal: number;
|
||||
let ajustementFinal: number | undefined;
|
||||
|
||||
if (rectifier) {
|
||||
const montant = typeof montantRaw === 'number' ? montantRaw : parseFloat(String(montantRaw ?? 0).replace(',', '.'));
|
||||
if (isNaN(montant) || montant < 0) {
|
||||
return NextResponse.json({ error: 'Budget invalide' }, { status: 400 });
|
||||
}
|
||||
montantFinal = montant;
|
||||
const aj = typeof ajRaw === 'number' ? ajRaw : parseFloat(String(ajRaw ?? 0).replace(',', '.'));
|
||||
ajustementFinal = isNaN(aj) ? 0 : aj;
|
||||
} else if (montantToAdd !== undefined && montantToAdd !== null) {
|
||||
const toAdd = typeof montantToAdd === 'number' ? montantToAdd : parseFloat(String(montantToAdd).replace(',', '.'));
|
||||
if (isNaN(toAdd) || toAdd <= 0) {
|
||||
return NextResponse.json({ error: 'Montant à ajouter invalide' }, { status: 400 });
|
||||
}
|
||||
montantFinal = (existing?.montant ?? 0) + toAdd;
|
||||
} else {
|
||||
const montant = typeof montantRaw === 'number' ? montantRaw : parseFloat(String(montantRaw ?? 0).replace(',', '.'));
|
||||
if (isNaN(montant) || montant < 0) {
|
||||
return NextResponse.json({ error: 'Montant invalide' }, { status: 400 });
|
||||
}
|
||||
montantFinal = montant;
|
||||
}
|
||||
|
||||
const updateData: { montant: number; ajustementConsomme?: number } = { montant: montantFinal };
|
||||
if (ajustementFinal !== undefined) {
|
||||
updateData.ajustementConsomme = ajustementFinal;
|
||||
}
|
||||
|
||||
const createData: { prescripteurValue: string; montant: number; ajustementConsomme?: number } = {
|
||||
prescripteurValue,
|
||||
montant: montantFinal,
|
||||
};
|
||||
if (ajustementFinal !== undefined) {
|
||||
createData.ajustementConsomme = ajustementFinal;
|
||||
}
|
||||
|
||||
const budget = await prisma.prescripteurBudget.upsert({
|
||||
where: { prescripteurValue },
|
||||
update: updateData,
|
||||
create: createData,
|
||||
});
|
||||
|
||||
return NextResponse.json(budget);
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la mise à jour du budget:', error);
|
||||
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,12 @@ export async function POST(
|
||||
const participation = await prisma.participationFinanciere.findUnique({
|
||||
where: { id: params.id },
|
||||
include: {
|
||||
adherent: { select: { prenom: true, nom: true } },
|
||||
trajet: { select: { date: true } },
|
||||
adherent: {
|
||||
select: { prenom: true, nom: true, email: true, facturation: true, prescripteur: true },
|
||||
},
|
||||
trajet: {
|
||||
select: { date: true, universProId: true, universPro: { select: { email: true, prenom: true, nom: true, nomEntreprise: true } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -27,6 +31,34 @@ export async function POST(
|
||||
return NextResponse.json({ error: 'Participation non trouvée' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Déterminer l'email selon la facturation de l'adhérent
|
||||
const facturation = (participation.adherent.facturation || '').trim();
|
||||
const isAdherent = !facturation || /^adh[eé]rent$/i.test(facturation);
|
||||
|
||||
let destinataireEmail: string;
|
||||
let destinataireNom: string;
|
||||
|
||||
if (isAdherent) {
|
||||
destinataireEmail = participation.adherent.email;
|
||||
destinataireNom = `${participation.adherent.prenom} ${participation.adherent.nom}`;
|
||||
} else if (participation.trajet?.universProId && participation.trajet?.universPro) {
|
||||
destinataireEmail = participation.trajet.universPro.email;
|
||||
destinataireNom = `${participation.trajet.universPro.prenom} ${participation.trajet.universPro.nom} - ${participation.trajet.universPro.nomEntreprise}`;
|
||||
} else {
|
||||
const universPro = await prisma.universPro.findFirst({
|
||||
where: { nomEntreprise: facturation },
|
||||
});
|
||||
if (universPro) {
|
||||
destinataireEmail = universPro.email;
|
||||
destinataireNom = `${universPro.prenom} ${universPro.nom} - ${universPro.nomEntreprise}`;
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ error: `Aucune fiche Univers Pro trouvée pour "${facturation}". Créez un contact avec ce nom d'entreprise ou vérifiez la facturation de l'adhérent.` },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const filePath = getParticipationStoragePath(participation.id);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return NextResponse.json(
|
||||
@@ -72,7 +104,7 @@ export async function POST(
|
||||
|
||||
await transporter.sendMail({
|
||||
from: process.env.SMTP_FROM || smtpUser,
|
||||
to: participation.destinataireEmail,
|
||||
to: 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: `
|
||||
@@ -88,14 +120,19 @@ export async function POST(
|
||||
],
|
||||
});
|
||||
|
||||
// Mettre à jour le statut en "envoyé"
|
||||
// Mettre à jour le statut en "envoyé" et le destinataire (au cas où il aurait changé)
|
||||
await prisma.participationFinanciere.update({
|
||||
where: { id: params.id },
|
||||
data: { statut: 'envoye' },
|
||||
data: {
|
||||
statut: 'envoye',
|
||||
destinataireEmail,
|
||||
destinataireNom,
|
||||
destinataireType: isAdherent ? 'adherent' : 'univers_pro',
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
message: `Participation envoyée à ${participation.destinataireEmail}`,
|
||||
message: `Participation envoyée à ${destinataireEmail}. Le budget du prescripteur a été décrementé.`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de l\'envoi de l\'email:', error);
|
||||
|
||||
Reference in New Issue
Block a user