espace-paie-odentas/app/api/webhooks/docuseal-amendment/route.ts
odentas 5b72941777 feat: Système complet de gestion des avenants avec signatures électroniques
 Nouvelles fonctionnalités
- Page de gestion des avenants (/staff/avenants)
- Page de détail d'un avenant (/staff/avenants/[id])
- Création d'avenants (objet, durée, rémunération)
- Génération automatique de PDF d'avenant
- Signature électronique via DocuSeal (employeur puis salarié)
- Changement manuel du statut d'un avenant
- Suppression d'avenants

🔧 Routes API
- POST /api/staff/amendments/create - Créer un avenant
- POST /api/staff/amendments/generate-pdf - Générer le PDF
- POST /api/staff/amendments/[id]/send-signature - Envoyer en signature
- POST /api/staff/amendments/[id]/change-status - Changer le statut
- POST /api/webhooks/docuseal-amendment - Webhook après signature employeur
- GET /api/signatures-electroniques/avenants - Liste des avenants en signature

📧 Système email universel v2
- Migration vers le système universel v2 pour les emails d'avenants
- Template 'signature-request-employee-amendment' pour salariés
- Insertion automatique dans DynamoDB pour la Lambda
- Mise à jour automatique du statut dans Supabase

🗄️ Base de données
- Table 'avenants' avec tous les champs (objet, durée, rémunération)
- Colonnes de notification (last_employer_notification_at, last_employee_notification_at)
- Liaison avec cddu_contracts

🎨 Composants
- AvenantDetailPageClient - Détail complet d'un avenant
- ChangeStatusModal - Changement de statut manuel
- SendSignatureModal - Envoi en signature
- DeleteAvenantModal - Suppression avec confirmation
- AvenantSuccessModal - Confirmation de création

📚 Documentation
- AVENANT_EMAIL_SYSTEM_MIGRATION.md - Guide complet de migration

🐛 Corrections
- Fix parsing défensif dans Lambda AWS
- Fix récupération des données depuis DynamoDB
- Fix statut MFA !== 'verified' au lieu de === 'unverified'
2025-10-23 15:30:11 +02:00

179 lines
5.8 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 pour gérer les signatures d'avenants via DocuSeal
* Appelé après que l'employeur a signé un avenant
*
* Flux :
* 1. Employeur signe via DocuSeal
* 2. DocuSeal → Lambda postDocuSealAvenantSalarie → Cette route API
* 3. Cette route :
* - Met à jour le statut de l'avenant dans Supabase
* - Envoie l'email au salarié via le système universel v2
* - Retourne les infos pour logging
*/
export async function POST(request: NextRequest) {
console.log("🔔 [WEBHOOK AVENANT] Début du traitement");
try {
const body = await request.json();
console.log("📦 [WEBHOOK AVENANT] Body reçu:", JSON.stringify(body, null, 2));
const {
documentName, // Numéro de l'avenant (ex: AVE-001)
employeeSlug, // Slug DocuSeal du salarié
submissionId, // ID de la soumission DocuSeal
employeeEmail, // Email du salarié
// Données pour l'email
reference, // Référence du contrat
salarie, // Nom complet du salarié
date, // Date de début
poste, // Poste/profession
analytique, // Production
structure, // Nom de l'organisation
prenom_salarie,
prenom_signataire,
code_employeur,
matricule,
} = body;
// Validation des champs requis
if (!documentName || !employeeSlug || !employeeEmail) {
console.error("❌ [WEBHOOK AVENANT] Champs manquants:", {
documentName: !!documentName,
employeeSlug: !!employeeSlug,
employeeEmail: !!employeeEmail,
});
return NextResponse.json(
{ error: "Champs requis manquants" },
{ status: 400 }
);
}
const supabase = createSbServiceRole();
// 1. Trouver l'avenant par son numéro
console.log("🔍 [WEBHOOK AVENANT] Recherche de l'avenant:", documentName);
const { data: avenant, error: avenantError } = await supabase
.from("avenants")
.select(`
*,
cddu_contracts (
*,
salaries (
prenom,
nom,
adresse_mail
),
organizations (
id,
name
)
)
`)
.eq("numero_avenant", documentName)
.maybeSingle();
if (avenantError || !avenant) {
console.error("❌ [WEBHOOK AVENANT] Avenant non trouvé:", avenantError);
return NextResponse.json(
{ error: "Avenant non trouvé", details: avenantError?.message },
{ status: 404 }
);
}
console.log("✅ [WEBHOOK AVENANT] Avenant trouvé:", {
id: avenant.id,
numero: avenant.numero_avenant,
statut_actuel: avenant.signature_status,
});
// 2. Mettre à jour le statut de l'avenant
console.log("🔄 [WEBHOOK AVENANT] Mise à jour du statut...");
const { error: updateError } = await supabase
.from("avenants")
.update({
signature_status: "pending_employee", // Employeur signé, en attente salarié
last_employee_notification_at: new Date().toISOString(),
})
.eq("id", avenant.id);
if (updateError) {
console.error("❌ [WEBHOOK AVENANT] Erreur mise à jour:", updateError);
// On continue quand même pour envoyer l'email
} else {
console.log("✅ [WEBHOOK AVENANT] Statut mis à jour: pending_employee");
}
// 3. Préparer les données pour l'email
const signatureLink = `https://paie.odentas.fr/odentas-sign?docuseal_id=${employeeSlug}`;
// Formater la date
const formatDate = (dateStr?: string) => {
if (!dateStr) return "-";
const [y, m, d] = dateStr.split("-");
return `${d}/${m}/${y}`;
};
const salarie_data = avenant.cddu_contracts?.salaries;
const contract_data = avenant.cddu_contracts;
const organization_data = contract_data?.organizations;
const emailData: EmailDataV2 = {
firstName: prenom_salarie || salarie_data?.prenom,
organizationName: structure || organization_data?.name,
employerCode: code_employeur || "Non spécifié",
matricule: matricule || "Non spécifié",
contractReference: reference || contract_data?.contract_number,
startDate: formatDate(date || contract_data?.date_debut),
profession: poste || contract_data?.fonction,
productionName: analytique || contract_data?.analytique,
ctaUrl: signatureLink,
numeroAvenant: avenant.numero_avenant,
contractType: "CDDU (contrat intermittent)",
};
// 4. Envoyer l'email au salarié via le système universel v2
console.log("📧 [WEBHOOK AVENANT] Envoi de l'email au salarié...");
console.log("📧 [WEBHOOK AVENANT] Email data:", emailData);
try {
const messageId = await sendUniversalEmailV2({
type: "signature-request-employee-amendment",
toEmail: employeeEmail,
data: emailData,
});
console.log("✅ [WEBHOOK AVENANT] Email envoyé avec succès:", messageId);
return NextResponse.json({
success: true,
message: "Email de signature envoyé au salarié",
messageId,
avenantId: avenant.id,
signatureLink,
});
} catch (emailError: any) {
console.error("❌ [WEBHOOK AVENANT] Erreur envoi email:", emailError);
return NextResponse.json(
{
error: "Erreur lors de l'envoi de l'email",
details: emailError.message,
avenantId: avenant.id,
},
{ status: 500 }
);
}
} catch (error: any) {
console.error("❌❌❌ [WEBHOOK AVENANT] ERREUR FATALE:", error);
console.error("Stack trace:", error.stack);
return NextResponse.json(
{ error: "Erreur serveur", details: error.message },
{ status: 500 }
);
}
}