309 lines
12 KiB
TypeScript
309 lines
12 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
||
import { createSbServiceRole } from "@/lib/supabaseServer";
|
||
import { sendUniversalEmailV2, EmailDataV2 } from "@/lib/emailTemplateService";
|
||
|
||
export const dynamic = "force-dynamic";
|
||
|
||
/**
|
||
* Webhook appelé quand TOUTES les parties ont signé l'avenant (employeur + salarié)
|
||
* Envoie les emails de confirmation finaux aux deux parties
|
||
*
|
||
* Flux :
|
||
* 1. Employeur signe → Lambda postDocuSealAvenantSalarie → /api/webhooks/docuseal-amendment
|
||
* 2. Salarié signe → DocuSeal webhook → Cette route
|
||
* 3. Envoi emails de confirmation à l'employeur ET au salarié
|
||
*/
|
||
export async function POST(request: NextRequest) {
|
||
console.log("🎉 [WEBHOOK AVENANT COMPLETED] Début du traitement");
|
||
|
||
try {
|
||
const body = await request.json();
|
||
console.log("📦 [WEBHOOK AVENANT COMPLETED] Body reçu:", JSON.stringify(body, null, 2));
|
||
|
||
// Extraire les données du webhook DocuSeal
|
||
const eventType = body.event_type;
|
||
const submissionId = body.data?.submission?.id || body.data?.submission_id;
|
||
const templateExternalId = body.data?.template?.external_id;
|
||
const submitters = body.data?.submitters || body.data?.submission?.submitters || [];
|
||
|
||
console.log("🔍 [WEBHOOK AVENANT COMPLETED] Event type:", eventType);
|
||
console.log("🔍 [WEBHOOK AVENANT COMPLETED] Submission ID:", submissionId);
|
||
console.log("🔍 [WEBHOOK AVENANT COMPLETED] Template external ID:", templateExternalId);
|
||
console.log("🔍 [WEBHOOK AVENANT COMPLETED] Submitters dans payload:", JSON.stringify(submitters, null, 2));
|
||
|
||
// Vérifier que c'est bien un événement de complétion FINALE (form.completed)
|
||
// "submission.completed" est envoyé à CHAQUE signature individuelle
|
||
// "form.completed" est envoyé UNE SEULE FOIS quand TOUS ont signé
|
||
if (eventType !== "form.completed") {
|
||
console.log("ℹ️ [WEBHOOK AVENANT COMPLETED] Event type non géré (attendu: form.completed):", eventType);
|
||
return NextResponse.json({ message: "Event type ignoré - attendu form.completed" }, { status: 200 });
|
||
}
|
||
|
||
if (!submissionId) {
|
||
console.error("❌ [WEBHOOK AVENANT COMPLETED] submission_id manquant");
|
||
return NextResponse.json(
|
||
{ error: "submission_id requis" },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// Récupérer les submitters via l'API DocuSeal car le payload ne les contient pas tous
|
||
console.log("🔍 [WEBHOOK AVENANT COMPLETED] Récupération des submitters via API DocuSeal...");
|
||
let allSubmitters: any[] = [];
|
||
try {
|
||
const docusealResponse = await fetch(
|
||
`${process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'}/api/docuseal/submissions/${submissionId}`,
|
||
{
|
||
method: 'GET',
|
||
cache: 'no-store',
|
||
}
|
||
);
|
||
|
||
if (docusealResponse.ok) {
|
||
const submission = await docusealResponse.json();
|
||
allSubmitters = submission.submitters || [];
|
||
console.log("✅ [WEBHOOK AVENANT COMPLETED] Submitters récupérés:", allSubmitters.map((s: any) => ({
|
||
role: s.role,
|
||
email: s.email,
|
||
status: s.status
|
||
})));
|
||
} else {
|
||
console.error("❌ [WEBHOOK AVENANT COMPLETED] Erreur récupération submitters");
|
||
}
|
||
} catch (error) {
|
||
console.error("❌ [WEBHOOK AVENANT COMPLETED] Erreur API DocuSeal:", error);
|
||
}
|
||
|
||
// IMPORTANT: Vérifier que TOUS les submitters ont signé (status = 'completed')
|
||
const allSigned = allSubmitters.length > 0 && allSubmitters.every((s: any) => s.status === 'completed');
|
||
|
||
if (!allSigned) {
|
||
console.log("⏸️ [WEBHOOK AVENANT COMPLETED] Tous les submitters n'ont pas encore signé");
|
||
console.log("📋 [WEBHOOK AVENANT COMPLETED] Statuts:", allSubmitters.map((s: any) => ({
|
||
role: s.role,
|
||
email: s.email,
|
||
status: s.status
|
||
})));
|
||
return NextResponse.json({
|
||
message: "En attente de toutes les signatures",
|
||
submitters: allSubmitters.map((s: any) => ({ role: s.role, status: s.status }))
|
||
}, { status: 200 });
|
||
}
|
||
|
||
console.log("✅ [WEBHOOK AVENANT COMPLETED] Toutes les signatures sont complètes !");
|
||
|
||
const supabase = createSbServiceRole();
|
||
|
||
// 1. Récupérer l'avenant depuis Supabase via le docuseal_submission_id
|
||
console.log("🔍 [WEBHOOK AVENANT COMPLETED] Recherche de l'avenant...");
|
||
const { data: avenant, error: avenantError } = await supabase
|
||
.from("avenants")
|
||
.select(`
|
||
*,
|
||
cddu_contracts (
|
||
*,
|
||
salaries (
|
||
prenom,
|
||
nom,
|
||
adresse_mail
|
||
),
|
||
organizations (
|
||
id,
|
||
name
|
||
)
|
||
)
|
||
`)
|
||
.eq("docuseal_submission_id", submissionId)
|
||
.maybeSingle();
|
||
|
||
if (avenantError || !avenant) {
|
||
console.error("❌ [WEBHOOK AVENANT COMPLETED] Avenant non trouvé:", avenantError);
|
||
return NextResponse.json(
|
||
{ error: "Avenant non trouvé", details: avenantError?.message },
|
||
{ status: 404 }
|
||
);
|
||
}
|
||
|
||
console.log("✅ [WEBHOOK AVENANT COMPLETED] Avenant trouvé:", {
|
||
id: avenant.id,
|
||
numero: avenant.numero_avenant,
|
||
contract_id: avenant.contract_id,
|
||
});
|
||
|
||
// 2. Mettre à jour le statut de l'avenant
|
||
console.log("🔄 [WEBHOOK AVENANT COMPLETED] Mise à jour du statut...");
|
||
const { error: updateError } = await supabase
|
||
.from("avenants")
|
||
.update({
|
||
signature_status: "signed",
|
||
})
|
||
.eq("id", avenant.id);
|
||
|
||
if (updateError) {
|
||
console.error("❌ [WEBHOOK AVENANT COMPLETED] Erreur mise à jour avenant:", updateError);
|
||
} else {
|
||
console.log("✅ [WEBHOOK AVENANT COMPLETED] Statut avenant mis à jour: signed");
|
||
}
|
||
|
||
// 2b. Mettre à jour le contrat pour marquer l'avenant comme signé
|
||
if (avenant.contract_id) {
|
||
console.log("🔄 [WEBHOOK AVENANT COMPLETED] Mise à jour du contrat...");
|
||
const { error: contractUpdateError } = await supabase
|
||
.from("cddu_contracts")
|
||
.update({
|
||
avenant_signe: true,
|
||
avenant_signe_date: new Date().toISOString(),
|
||
})
|
||
.eq("id", avenant.contract_id);
|
||
|
||
if (contractUpdateError) {
|
||
console.error("❌ [WEBHOOK AVENANT COMPLETED] Erreur mise à jour contrat:", contractUpdateError);
|
||
} else {
|
||
console.log("✅ [WEBHOOK AVENANT COMPLETED] Contrat mis à jour: avenant_signe = true");
|
||
}
|
||
}
|
||
|
||
// 3. Préparer les données pour les emails
|
||
const contract = avenant.cddu_contracts;
|
||
const salarie = contract?.salaries;
|
||
const organization = contract?.organizations;
|
||
|
||
if (!contract || !salarie || !organization) {
|
||
console.error("❌ [WEBHOOK AVENANT COMPLETED] Données manquantes:", {
|
||
hasContract: !!contract,
|
||
hasSalarie: !!salarie,
|
||
hasOrganization: !!organization,
|
||
});
|
||
return NextResponse.json(
|
||
{ error: "Données du contrat/salarié/organisation manquantes" },
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// Formater la date
|
||
const formatDate = (dateStr?: string | null) => {
|
||
if (!dateStr) return "-";
|
||
try {
|
||
const parts = dateStr.split("-");
|
||
if (parts.length === 3) {
|
||
const [y, m, d] = parts;
|
||
return `${d}/${m}/${y}`;
|
||
}
|
||
return dateStr;
|
||
} catch {
|
||
return dateStr;
|
||
}
|
||
};
|
||
|
||
const startDate = formatDate(contract.start_date);
|
||
const employeeEmail = salarie.adresse_mail;
|
||
|
||
// Récupérer les infos employeur depuis organization_details
|
||
let employerCode = "Non spécifié";
|
||
let employerFirstName = "Employeur";
|
||
let employerEmail = "hello@odentas.fr";
|
||
|
||
if (organization.id) {
|
||
const { data: orgDetails } = await supabase
|
||
.from("organization_details")
|
||
.select("code_employeur, prenom_signataire, email_signature")
|
||
.eq("org_id", organization.id)
|
||
.maybeSingle();
|
||
|
||
if (orgDetails) {
|
||
if (orgDetails.code_employeur) {
|
||
employerCode = orgDetails.code_employeur;
|
||
}
|
||
if (orgDetails.prenom_signataire) {
|
||
employerFirstName = orgDetails.prenom_signataire;
|
||
}
|
||
if (orgDetails.email_signature) {
|
||
employerEmail = orgDetails.email_signature;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 4. Envoyer l'email à l'EMPLOYEUR
|
||
console.log("📧 [WEBHOOK AVENANT COMPLETED] Envoi email employeur...");
|
||
|
||
const employerEmailData: EmailDataV2 = {
|
||
firstName: employerFirstName,
|
||
organizationName: organization.name || "Votre structure",
|
||
employerCode: employerCode,
|
||
handlerName: "Renaud BREVIERE-ABRAHAM",
|
||
contractReference: contract.contract_number || "Non spécifié",
|
||
startDate: startDate,
|
||
profession: contract.profession || "Non spécifié",
|
||
productionName: contract.analytique || contract.production_name || "Non spécifié",
|
||
ctaUrl: "https://paie.odentas.fr/",
|
||
ctaText: "Accès à l'Espace Paie",
|
||
numeroAvenant: avenant.numero_avenant,
|
||
employeeName: `${salarie.prenom} ${salarie.nom}`,
|
||
matricule: contract.matricule || contract.employee_matricule || "Non spécifié",
|
||
contractType: "CDDU (contrat intermittent)",
|
||
};
|
||
|
||
try {
|
||
const employerMessageId = await sendUniversalEmailV2({
|
||
type: "amendment-completed-employer",
|
||
toEmail: employerEmail,
|
||
data: employerEmailData,
|
||
});
|
||
console.log("✅ [WEBHOOK AVENANT COMPLETED] Email employeur envoyé:", employerMessageId);
|
||
} catch (emailError: any) {
|
||
console.error("❌ [WEBHOOK AVENANT COMPLETED] Erreur email employeur:", emailError);
|
||
}
|
||
|
||
// 5. Envoyer l'email au SALARIÉ
|
||
console.log("📧 [WEBHOOK AVENANT COMPLETED] Envoi email salarié...");
|
||
|
||
// Récupérer le slug DocuSeal du salarié depuis l'avenant (stocké lors de la signature employeur)
|
||
const employeeDocusealSlug = avenant.employee_docuseal_slug || "";
|
||
|
||
console.log("🔑 [WEBHOOK AVENANT COMPLETED] Slug salarié:", employeeDocusealSlug);
|
||
|
||
const employeeEmailData: EmailDataV2 = {
|
||
firstName: salarie.prenom || "Salarié",
|
||
organizationName: organization.name || "Votre employeur",
|
||
matricule: contract.matricule || contract.employee_matricule || "Non spécifié",
|
||
contractReference: contract.contract_number || "Non spécifié",
|
||
startDate: startDate,
|
||
profession: contract.profession || "Non spécifié",
|
||
productionName: contract.analytique || contract.production_name || "Non spécifié",
|
||
ctaUrl: employeeDocusealSlug
|
||
? `https://paie.odentas.fr/signature-salarie/?docuseal_id=${employeeDocusealSlug}`
|
||
: `https://paie.odentas.fr/`,
|
||
ctaText: "Télécharger l'avenant",
|
||
numeroAvenant: avenant.numero_avenant,
|
||
contractType: "CDDU (contrat intermittent)",
|
||
};
|
||
|
||
try {
|
||
const employeeMessageId = await sendUniversalEmailV2({
|
||
type: "amendment-completed-employee",
|
||
toEmail: employeeEmail,
|
||
data: employeeEmailData,
|
||
});
|
||
console.log("✅ [WEBHOOK AVENANT COMPLETED] Email salarié envoyé:", employeeMessageId);
|
||
} catch (emailError: any) {
|
||
console.error("❌ [WEBHOOK AVENANT COMPLETED] Erreur email salarié:", emailError);
|
||
}
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
message: "Emails de confirmation envoyés",
|
||
avenantId: avenant.id,
|
||
numeroAvenant: avenant.numero_avenant,
|
||
});
|
||
|
||
} catch (error: any) {
|
||
console.error("❌ [WEBHOOK AVENANT COMPLETED] Erreur:", error);
|
||
return NextResponse.json(
|
||
{
|
||
error: "Erreur lors du traitement",
|
||
details: error.message,
|
||
},
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}
|