✨ 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'
239 lines
7.5 KiB
TypeScript
239 lines
7.5 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
||
import { sendUniversalEmailV2 } from '@/lib/emailTemplateService';
|
||
import { ENV } from '@/lib/cleanEnv';
|
||
import { createSbServiceRole } from '@/lib/supabaseServer';
|
||
|
||
/**
|
||
* POST /api/emails/signature-avenant-salarie
|
||
*
|
||
* Route API appelée par la Lambda postDocuSealAvenantSalarie pour envoyer l'email de signature au salarié
|
||
* Utilise le système universel d'emails v2 avec logging automatique
|
||
*
|
||
* Authentification : API Key dans le header X-API-Key
|
||
*/
|
||
export async function POST(request: NextRequest) {
|
||
console.log('=== API Email Signature Avenant Salarié ===');
|
||
|
||
try {
|
||
// 1. Vérification de l'authentification via API Key
|
||
const apiKey = request.headers.get('X-API-Key');
|
||
const validApiKey = ENV.LAMBDA_API_KEY;
|
||
|
||
if (!validApiKey) {
|
||
console.error('❌ Configuration error: LAMBDA_API_KEY not configured');
|
||
return NextResponse.json(
|
||
{ error: 'Server configuration error' },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
|
||
if (!apiKey || apiKey !== validApiKey) {
|
||
console.error('❌ Unauthorized: Invalid or missing API key');
|
||
return NextResponse.json(
|
||
{ error: 'Unauthorized' },
|
||
{ status: 401 }
|
||
);
|
||
}
|
||
|
||
console.log('✅ Authentication successful');
|
||
|
||
// 2. Récupération et validation des données
|
||
const data = await request.json();
|
||
console.log('📦 Données reçues:', {
|
||
employeeEmail: data.employeeEmail,
|
||
reference: data.reference,
|
||
structure: data.structure,
|
||
firstName: data.firstName,
|
||
matricule: data.matricule,
|
||
avenantId: data.avenantId
|
||
});
|
||
|
||
const {
|
||
employeeEmail,
|
||
signatureLink,
|
||
reference,
|
||
firstName: providedFirstName,
|
||
organizationName: providedOrgName,
|
||
matricule,
|
||
typecontrat,
|
||
startDate,
|
||
profession,
|
||
productionName,
|
||
organizationId,
|
||
contractId,
|
||
avenantId,
|
||
numeroAvenant
|
||
} = data;
|
||
|
||
// Validation des champs requis
|
||
if (!employeeEmail || !signatureLink || !reference) {
|
||
console.error('❌ Champs requis manquants');
|
||
return NextResponse.json(
|
||
{
|
||
error: 'Champs manquants',
|
||
required: ['employeeEmail', 'signatureLink', 'reference']
|
||
},
|
||
{ status: 400 }
|
||
);
|
||
}
|
||
|
||
// 3. Récupération du vrai nom de l'organisation depuis Supabase
|
||
let organizationName = providedOrgName || 'Employeur';
|
||
|
||
if (organizationId || contractId) {
|
||
console.log('🔍 Récupération du nom de l\'organisation depuis la base de données...');
|
||
try {
|
||
const supabase = createSbServiceRole();
|
||
|
||
let actualOrgId = organizationId;
|
||
|
||
// Si on n'a que le contractId, récupérer l'org_id depuis le contrat
|
||
if (!actualOrgId && contractId) {
|
||
const { data: contractData } = await supabase
|
||
.from('cddu_contracts')
|
||
.select('org_id')
|
||
.eq('id', contractId)
|
||
.single();
|
||
|
||
if (contractData?.org_id) {
|
||
actualOrgId = contractData.org_id;
|
||
console.log('✅ org_id récupéré depuis le contrat:', actualOrgId);
|
||
}
|
||
}
|
||
|
||
// Récupérer le nom de l'organisation
|
||
if (actualOrgId) {
|
||
const { data: orgData, error: orgError } = await supabase
|
||
.from('organizations')
|
||
.select('name')
|
||
.eq('id', actualOrgId)
|
||
.single();
|
||
|
||
if (!orgError && orgData?.name) {
|
||
organizationName = orgData.name;
|
||
console.log('✅ Nom de l\'organisation trouvé:', organizationName);
|
||
} else {
|
||
console.warn('⚠️ Nom de l\'organisation non trouvé, utilisation du fallback');
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('⚠️ Erreur lors de la récupération du nom de l\'organisation:', err);
|
||
}
|
||
}
|
||
|
||
// 4. Récupération du prénom depuis Supabase si non fourni
|
||
let firstName = providedFirstName;
|
||
|
||
if (!firstName && (matricule || employeeEmail)) {
|
||
console.log('🔍 Récupération du prénom depuis la table salaries...');
|
||
try {
|
||
const supabase = createSbServiceRole();
|
||
|
||
// Recherche par matricule ou email
|
||
let query = supabase
|
||
.from('salaries')
|
||
.select('prenom')
|
||
.limit(1);
|
||
|
||
if (organizationId) {
|
||
query = query.eq('employer_id', organizationId);
|
||
}
|
||
|
||
// Priorité au matricule
|
||
if (matricule) {
|
||
query = query.or(`code_salarie.eq.${matricule},num_salarie.eq.${matricule}`);
|
||
} else if (employeeEmail) {
|
||
query = query.eq('adresse_mail', employeeEmail);
|
||
}
|
||
|
||
const { data: salaryData, error: salaryError } = await query;
|
||
|
||
if (!salaryError && salaryData && salaryData[0]?.prenom) {
|
||
firstName = salaryData[0].prenom;
|
||
console.log('✅ Prénom trouvé dans Supabase:', firstName);
|
||
} else {
|
||
console.warn('⚠️ Prénom non trouvé dans Supabase');
|
||
firstName = 'Salarié(e)'; // Fallback
|
||
}
|
||
} catch (err) {
|
||
console.error('⚠️ Erreur lors de la récupération du prénom:', err);
|
||
firstName = 'Salarié(e)'; // Fallback
|
||
}
|
||
} else if (!firstName) {
|
||
firstName = 'Salarié(e)'; // Fallback si aucune donnée disponible
|
||
}
|
||
|
||
// 5. Mise à jour du last_employee_notification_at dans la table avenants
|
||
if (avenantId) {
|
||
console.log('🔄 Mise à jour de last_employee_notification_at pour l\'avenant:', avenantId);
|
||
try {
|
||
const supabase = createSbServiceRole();
|
||
const { error: updateError } = await supabase
|
||
.from('avenants')
|
||
.update({
|
||
last_employee_notification_at: new Date().toISOString(),
|
||
signature_status: 'pending_employee'
|
||
})
|
||
.eq('id', avenantId);
|
||
|
||
if (updateError) {
|
||
console.error('❌ Erreur mise à jour last_employee_notification_at:', updateError);
|
||
} else {
|
||
console.log('✅ last_employee_notification_at mis à jour');
|
||
}
|
||
} catch (err) {
|
||
console.error('⚠️ Erreur lors de la mise à jour:', err);
|
||
}
|
||
}
|
||
|
||
// 6. Envoi de l'email via le système universel v2
|
||
console.log('📧 Envoi de l\'email via le système universel v2...');
|
||
|
||
try {
|
||
await sendUniversalEmailV2({
|
||
type: 'signature-request-employee-amendment',
|
||
toEmail: employeeEmail,
|
||
subject: `Signature électronique requise – Avenant ${numeroAvenant || reference}`,
|
||
data: {
|
||
signatureLink,
|
||
firstName,
|
||
organizationName,
|
||
matricule,
|
||
contractReference: reference,
|
||
contractType: typecontrat || 'CDDU',
|
||
startDate,
|
||
profession,
|
||
productionName,
|
||
numeroAvenant
|
||
}
|
||
});
|
||
|
||
console.log('✅ Email envoyé avec succès');
|
||
|
||
return NextResponse.json({
|
||
success: true,
|
||
message: 'Email envoyé avec succès'
|
||
});
|
||
|
||
} catch (emailError: any) {
|
||
console.error('❌ Erreur lors de l\'envoi de l\'email:', emailError);
|
||
return NextResponse.json(
|
||
{
|
||
error: 'Erreur lors de l\'envoi de l\'email',
|
||
details: emailError.message
|
||
},
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
|
||
} catch (error: any) {
|
||
console.error('❌ Erreur serveur:', error);
|
||
return NextResponse.json(
|
||
{
|
||
error: 'Erreur serveur',
|
||
details: error.message
|
||
},
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}
|