- Tous les clients repliés par défaut à l'ouverture du modal - Boutons 'Tout replier' / 'Tout déplier' pour gérer tous les clients - Section factures repliable avec bouton Afficher/Masquer - Affichage résumé facture sélectionnée quand section repliée - Nouveau client déplié automatiquement pour faciliter la saisie - Améliore la lisibilité pour NAA avec nombreux clients
266 lines
7.7 KiB
TypeScript
266 lines
7.7 KiB
TypeScript
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
|
|
import { cookies } from "next/headers";
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
// GET - Récupérer les détails d'une NAA avec toutes ses données
|
|
export async function GET(
|
|
request: NextRequest,
|
|
{ params }: { params: { id: string } }
|
|
) {
|
|
try {
|
|
const cookieStore = cookies();
|
|
const supabase = createRouteHandlerClient({ cookies: () => cookieStore });
|
|
|
|
// Vérifier l'authentification staff
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
if (!user) {
|
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
|
}
|
|
|
|
const { data: staffUser } = await supabase
|
|
.from("staff_users")
|
|
.select("user_id")
|
|
.eq("user_id", user.id)
|
|
.single();
|
|
|
|
if (!staffUser) {
|
|
return NextResponse.json({ error: "Accès non autorisé" }, { status: 403 });
|
|
}
|
|
|
|
// Récupérer le document NAA
|
|
const { data: naaDoc, error: naaError } = await supabase
|
|
.from("naa_documents")
|
|
.select("*")
|
|
.eq("id", params.id)
|
|
.single();
|
|
|
|
if (naaError || !naaDoc) {
|
|
return NextResponse.json({ error: "NAA non trouvée" }, { status: 404 });
|
|
}
|
|
|
|
// Récupérer les prestations
|
|
const { data: prestations, error: prestationsError } = await supabase
|
|
.from("naa_prestations")
|
|
.select("*")
|
|
.eq("naa_id", params.id)
|
|
.order("created_at");
|
|
|
|
if (prestationsError) {
|
|
console.error("Error fetching prestations:", prestationsError);
|
|
}
|
|
|
|
return NextResponse.json({
|
|
...naaDoc,
|
|
prestations: prestations || []
|
|
});
|
|
|
|
} catch (error: any) {
|
|
console.error("Error GET /api/staff/naa/[id]:", error);
|
|
return NextResponse.json(
|
|
{ error: error.message || "Erreur serveur" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// DELETE - Supprimer une NAA
|
|
export async function DELETE(
|
|
request: NextRequest,
|
|
{ params }: { params: { id: string } }
|
|
) {
|
|
try {
|
|
const cookieStore = cookies();
|
|
const supabase = createRouteHandlerClient({ cookies: () => cookieStore });
|
|
|
|
// Vérifier l'authentification staff
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
if (!user) {
|
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
|
}
|
|
|
|
const { data: staffUser } = await supabase
|
|
.from("staff_users")
|
|
.select("user_id")
|
|
.eq("user_id", user.id)
|
|
.single();
|
|
|
|
if (!staffUser) {
|
|
return NextResponse.json({ error: "Accès non autorisé" }, { status: 403 });
|
|
}
|
|
|
|
// Supprimer le document NAA (les prestations et line items seront supprimés en cascade)
|
|
const { error } = await supabase
|
|
.from("naa_documents")
|
|
.delete()
|
|
.eq("id", params.id);
|
|
|
|
if (error) {
|
|
console.error("Error deleting NAA:", error);
|
|
return NextResponse.json({ error: error.message }, { status: 500 });
|
|
}
|
|
|
|
return NextResponse.json({ success: true });
|
|
|
|
} catch (error: any) {
|
|
console.error("Error DELETE /api/staff/naa/[id]:", error);
|
|
return NextResponse.json(
|
|
{ error: error.message || "Erreur serveur" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// PUT - Mettre à jour une NAA et ses prestations
|
|
export async function PUT(
|
|
request: NextRequest,
|
|
{ params }: { params: { id: string } }
|
|
) {
|
|
try {
|
|
const cookieStore = cookies();
|
|
const supabase = createRouteHandlerClient({ cookies: () => cookieStore });
|
|
|
|
// Vérifier l'authentification staff
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
if (!user) {
|
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
|
}
|
|
|
|
const { data: staffUser } = await supabase
|
|
.from("staff_users")
|
|
.select("user_id")
|
|
.eq("user_id", user.id)
|
|
.single();
|
|
|
|
if (!staffUser) {
|
|
return NextResponse.json({ error: "Accès non autorisé" }, { status: 403 });
|
|
}
|
|
|
|
const body = await request.json();
|
|
const {
|
|
prestations,
|
|
solde_compte_apporteur,
|
|
deposit,
|
|
included_invoices
|
|
} = body;
|
|
|
|
// Vérifier que la NAA existe
|
|
const { data: naaDoc, error: naaCheckError } = await supabase
|
|
.from("naa_documents")
|
|
.select("*")
|
|
.eq("id", params.id)
|
|
.single();
|
|
|
|
if (naaCheckError || !naaDoc) {
|
|
return NextResponse.json({ error: "NAA non trouvée" }, { status: 404 });
|
|
}
|
|
|
|
// 1. Récupérer les IDs des prestations existantes
|
|
const { data: existingPrestations } = await supabase
|
|
.from("naa_prestations")
|
|
.select("id")
|
|
.eq("naa_id", params.id);
|
|
|
|
const existingIds = existingPrestations?.map(p => p.id) || [];
|
|
|
|
// 2. Identifier les prestations à conserver (celles qui ont un ID existant)
|
|
const prestationsWithIds = prestations.filter((p: any) => p.id && existingIds.includes(p.id));
|
|
const prestationIdsToKeep = prestationsWithIds.map((p: any) => p.id);
|
|
|
|
// 3. Supprimer les prestations qui ne sont plus dans la liste
|
|
const idsToDelete = existingIds.filter(id => !prestationIdsToKeep.includes(id));
|
|
|
|
if (idsToDelete.length > 0) {
|
|
await supabase
|
|
.from("naa_prestations")
|
|
.delete()
|
|
.in("id", idsToDelete);
|
|
}
|
|
|
|
// 4. Mettre à jour les prestations existantes qui ont changé
|
|
for (const prest of prestationsWithIds) {
|
|
await supabase
|
|
.from("naa_prestations")
|
|
.update({
|
|
client_name: prest.client,
|
|
client_code: prest.code,
|
|
type_prestation: prest.type_prestation,
|
|
quantite: prest.quantite,
|
|
tarif: prest.tarif,
|
|
total: prest.total
|
|
})
|
|
.eq("id", prest.id);
|
|
}
|
|
|
|
// 5. Insérer les nouvelles prestations (celles sans ID)
|
|
const newPrestations = prestations.filter((p: any) => !p.id);
|
|
|
|
if (newPrestations.length > 0) {
|
|
const prestationsToInsert = newPrestations.map((p: any) => ({
|
|
naa_id: params.id,
|
|
client_name: p.client,
|
|
client_code: p.code,
|
|
type_prestation: p.type_prestation,
|
|
quantite: p.quantite,
|
|
tarif: p.tarif,
|
|
total: p.total
|
|
}));
|
|
|
|
await supabase.from("naa_prestations").insert(prestationsToInsert);
|
|
}
|
|
|
|
// 6. Calculer les nouveaux totaux
|
|
const nbrePrestations = prestations.length;
|
|
const uniqueClients = [...new Set(prestations.map((p: any) => p.code))];
|
|
const nbreClients = uniqueClients.length;
|
|
|
|
// 7. Mettre à jour le document NAA avec les nouveaux totaux
|
|
await supabase
|
|
.from("naa_documents")
|
|
.update({
|
|
nbre_prestations: nbrePrestations,
|
|
nbre_clients: nbreClients,
|
|
solde_compte_apporteur: solde_compte_apporteur || 0,
|
|
deposit: deposit || 0,
|
|
updated_at: new Date().toISOString(),
|
|
status: "draft" // Remettre en draft car le PDF doit être régénéré
|
|
})
|
|
.eq("id", params.id);
|
|
|
|
// 8. Régénérer le PDF
|
|
const regenerateRes = await fetch(
|
|
`${request.nextUrl.origin}/api/staff/naa/${params.id}/regenerate`,
|
|
{
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
cookie: request.headers.get("cookie") || ""
|
|
},
|
|
body: JSON.stringify({ included_invoices })
|
|
}
|
|
);
|
|
|
|
if (!regenerateRes.ok) {
|
|
console.error("Erreur lors de la régénération du PDF");
|
|
return NextResponse.json({
|
|
success: true,
|
|
warning: "NAA mise à jour mais erreur lors de la régénération du PDF"
|
|
});
|
|
}
|
|
|
|
const regenerateData = await regenerateRes.json();
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
presigned_url: regenerateData.presigned_url
|
|
});
|
|
|
|
} catch (error: any) {
|
|
console.error("Error PUT /api/staff/naa/[id]:", error);
|
|
return NextResponse.json(
|
|
{ error: error.message || "Erreur serveur" },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|