437 lines
20 KiB
JavaScript
437 lines
20 KiB
JavaScript
/**
|
|
* Générateur de PDF pour les entités enrichies
|
|
* Génère des PDF formatés pour les personnes, adresses et entreprises
|
|
*/
|
|
|
|
const fs = require('fs').promises;
|
|
const path = require('path');
|
|
|
|
/**
|
|
* Génère un PDF pour une personne (Bodacc - gel des avoirs)
|
|
* @param {Object} personData - Données de la personne
|
|
* @param {Object} bodaccResult - Résultat de la recherche Bodacc
|
|
* @param {string} outputPath - Chemin de sortie du PDF
|
|
* @returns {Promise<string>} Chemin du PDF généré
|
|
*/
|
|
async function generatePersonPdf(personData, bodaccResult, outputPath) {
|
|
try {
|
|
const pdfContent = generatePersonPdfContent(personData, bodaccResult);
|
|
await fs.writeFile(outputPath, pdfContent, 'utf8');
|
|
console.log(`[PDF] Personne généré: ${outputPath}`);
|
|
return outputPath;
|
|
} catch (error) {
|
|
console.error(`[PDF] Erreur génération personne:`, error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Génère un PDF pour une entreprise (Inforgreffe/Societe.com)
|
|
* @param {Object} companyData - Données de l'entreprise
|
|
* @param {Object} inforgreffeResult - Résultat de la recherche Inforgreffe
|
|
* @param {string} outputPath - Chemin de sortie du PDF
|
|
* @returns {Promise<string>} Chemin du PDF généré
|
|
*/
|
|
async function generateCompanyPdf(companyData, inforgreffeResult, outputPath) {
|
|
try {
|
|
const pdfContent = generateCompanyPdfContent(companyData, inforgreffeResult);
|
|
await fs.writeFile(outputPath, pdfContent, 'utf8');
|
|
console.log(`[PDF] Entreprise généré: ${outputPath}`);
|
|
return outputPath;
|
|
} catch (error) {
|
|
console.error(`[PDF] Erreur génération entreprise:`, error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Génère un PDF pour une adresse (Cadastre/GéoRisque)
|
|
* @param {Object} addressData - Données de l'adresse
|
|
* @param {Object} geoResult - Résultat de la recherche géographique
|
|
* @param {string} outputPath - Chemin de sortie du PDF
|
|
* @returns {Promise<string>} Chemin du PDF généré
|
|
*/
|
|
async function generateAddressPdf(addressData, geoResult, outputPath) {
|
|
try {
|
|
const pdfContent = generateAddressPdfContent(addressData, geoResult);
|
|
await fs.writeFile(outputPath, pdfContent, 'utf8');
|
|
console.log(`[PDF] Adresse généré: ${outputPath}`);
|
|
return outputPath;
|
|
} catch (error) {
|
|
console.error(`[PDF] Erreur génération adresse:`, error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Génère le contenu HTML pour le PDF d'une personne
|
|
* @param {Object} personData - Données de la personne
|
|
* @param {Object} bodaccResult - Résultat Bodacc
|
|
* @returns {string} Contenu HTML
|
|
*/
|
|
function generatePersonPdfContent(personData, bodaccResult) {
|
|
const summary = bodaccResult.summary || {};
|
|
const results = bodaccResult.results || [];
|
|
|
|
return `
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Rapport Bodacc - ${personData.firstName} ${personData.lastName}</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; }
|
|
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
|
|
.title { color: #333; margin: 0; }
|
|
.subtitle { color: #666; margin: 5px 0 0 0; }
|
|
.section { margin: 20px 0; }
|
|
.section h2 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 5px; }
|
|
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
|
.info-item { background: #f9f9f9; padding: 15px; border-radius: 5px; }
|
|
.info-label { font-weight: bold; color: #555; }
|
|
.info-value { margin-top: 5px; }
|
|
.risk-high { color: #e74c3c; font-weight: bold; }
|
|
.risk-medium { color: #f39c12; font-weight: bold; }
|
|
.risk-low { color: #27ae60; font-weight: bold; }
|
|
.result-item { background: #fff; border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 5px; }
|
|
.result-date { color: #666; font-size: 0.9em; }
|
|
.result-score { background: #3498db; color: white; padding: 2px 8px; border-radius: 3px; font-size: 0.8em; }
|
|
.footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 0.9em; }
|
|
.no-results { text-align: center; color: #27ae60; font-style: italic; padding: 20px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1 class="title">Rapport Bodacc - Gel des avoirs</h1>
|
|
<p class="subtitle">Généré le ${new Date().toLocaleDateString('fr-FR')} à ${new Date().toLocaleTimeString('fr-FR')}</p>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Identité recherchée</h2>
|
|
<div class="info-grid">
|
|
<div class="info-item">
|
|
<div class="info-label">Nom complet</div>
|
|
<div class="info-value">${personData.firstName} ${personData.lastName}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Date de naissance</div>
|
|
<div class="info-value">${personData.birthDate || 'Non renseignée'}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Résumé de la recherche</h2>
|
|
<div class="info-grid">
|
|
<div class="info-item">
|
|
<div class="info-label">Statut</div>
|
|
<div class="info-value class="risk-${summary.riskLevel?.toLowerCase() || 'low'}">${summary.riskLevel || 'Faible'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Résultats trouvés</div>
|
|
<div class="info-value">${summary.totalResults || 0}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Haute confiance</div>
|
|
<div class="info-value">${summary.highConfidenceResults || 0}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Récents (1 an)</div>
|
|
<div class="info-value">${summary.recentResults || 0}</div>
|
|
</div>
|
|
</div>
|
|
<p><strong>Synthèse:</strong> ${summary.summary || 'Aucune donnée disponible'}</p>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Détail des résultats</h2>
|
|
${results.length === 0 ?
|
|
'<div class="no-results">✅ Aucune mention de gel des avoirs trouvée</div>' :
|
|
results.map(result => `
|
|
<div class="result-item">
|
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
<h3 style="margin: 0;">${result.name}</h3>
|
|
<span class="result-score">Score: ${(result.matchScore * 100).toFixed(0)}%</span>
|
|
</div>
|
|
<div class="result-date">Date: ${result.date}</div>
|
|
<p><strong>Description:</strong> ${result.description}</p>
|
|
<p><strong>Source:</strong> <a href="${result.sourceUrl}" target="_blank">${result.sourceUrl}</a></p>
|
|
</div>
|
|
`).join('')
|
|
}
|
|
</div>
|
|
|
|
<div class="footer">
|
|
<p><strong>Source:</strong> ${bodaccResult.source || 'Bodacc.fr'}</p>
|
|
<p><strong>Recherche effectuée le:</strong> ${bodaccResult.timestamp ? new Date(bodaccResult.timestamp).toLocaleString('fr-FR') : 'Non disponible'}</p>
|
|
<p><strong>Durée de la recherche:</strong> ${bodaccResult.duration || 0}ms</p>
|
|
<p><em>Ce rapport a été généré automatiquement par 4NK IA Front. Les informations sont fournies à titre indicatif.</em></p>
|
|
</div>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
/**
|
|
* Génère le contenu HTML pour le PDF d'une entreprise
|
|
* @param {Object} companyData - Données de l'entreprise
|
|
* @param {Object} inforgreffeResult - Résultat Inforgreffe
|
|
* @returns {string} Contenu HTML
|
|
*/
|
|
function generateCompanyPdfContent(companyData, inforgreffeResult) {
|
|
const company = inforgreffeResult.company || {};
|
|
const sources = inforgreffeResult.sources || {};
|
|
|
|
return `
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Rapport Entreprise - ${company.name || companyData.name}</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; }
|
|
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
|
|
.title { color: #333; margin: 0; }
|
|
.subtitle { color: #666; margin: 5px 0 0 0; }
|
|
.section { margin: 20px 0; }
|
|
.section h2 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 5px; }
|
|
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
|
.info-item { background: #f9f9f9; padding: 15px; border-radius: 5px; }
|
|
.info-label { font-weight: bold; color: #555; }
|
|
.info-value { margin-top: 5px; }
|
|
.status-ok { color: #27ae60; font-weight: bold; }
|
|
.status-partial { color: #f39c12; font-weight: bold; }
|
|
.status-missing { color: #e74c3c; font-weight: bold; }
|
|
.dirigeant-item { background: #fff; border: 1px solid #ddd; padding: 10px; margin: 5px 0; border-radius: 5px; }
|
|
.footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 0.9em; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1 class="title">Rapport Entreprise</h1>
|
|
<p class="subtitle">Généré le ${new Date().toLocaleDateString('fr-FR')} à ${new Date().toLocaleTimeString('fr-FR')}</p>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Informations générales</h2>
|
|
<div class="info-grid">
|
|
<div class="info-item">
|
|
<div class="info-label">Raison sociale</div>
|
|
<div class="info-value">${company.name || companyData.name || 'Non renseignée'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">SIREN</div>
|
|
<div class="info-value class="status-${company.siren ? 'ok' : 'missing'}">${company.siren || 'Non trouvé'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">SIRET</div>
|
|
<div class="info-value class="status-${company.siret ? 'ok' : 'missing'}">${company.siret || 'Non trouvé'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Forme juridique</div>
|
|
<div class="info-value class="status-${company.forme ? 'ok' : 'missing'}">${company.forme || 'Non renseignée'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Capital social</div>
|
|
<div class="info-value class="status-${company.capital ? 'ok' : 'missing'}">${company.capital || 'Non renseigné'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Date de création</div>
|
|
<div class="info-value class="status-${company.dateCreation ? 'ok' : 'missing'}">${company.dateCreation || 'Non renseignée'}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Adresse</h2>
|
|
<div class="info-item">
|
|
<div class="info-label">Adresse complète</div>
|
|
<div class="info-value class="status-${company.adresse ? 'ok' : 'missing'}">${company.adresse || 'Non renseignée'}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Activité</h2>
|
|
<div class="info-item">
|
|
<div class="info-label">Secteur d'activité</div>
|
|
<div class="info-value class="status-${company.activite ? 'ok' : 'missing'}">${company.activite || 'Non renseignée'}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Dirigeants (${company.dirigeants?.length || 0})</h2>
|
|
${company.dirigeants && company.dirigeants.length > 0 ?
|
|
company.dirigeants.map(dirigeant => `
|
|
<div class="dirigeant-item">
|
|
<strong>${dirigeant.nom}</strong> - ${dirigeant.fonction || 'Dirigeant'}
|
|
<br><small>Source: ${dirigeant.source || 'Non spécifiée'}</small>
|
|
</div>
|
|
`).join('') :
|
|
'<p class="status-missing">Aucun dirigeant trouvé</p>'
|
|
}
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Sources consultées</h2>
|
|
<div class="info-grid">
|
|
<div class="info-item">
|
|
<div class="info-label">Societe.com</div>
|
|
<div class="info-value class="status-${sources.societeCom?.success ? 'ok' : 'missing'}">${sources.societeCom?.success ? '✅ Consulté' : '❌ Non accessible'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Inforgreffe</div>
|
|
<div class="info-value class="status-${sources.inforgreffe?.success ? 'ok' : 'missing'}">${sources.inforgreffe?.success ? '✅ Consulté' : '❌ Non accessible'}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
<p><strong>Recherche effectuée le:</strong> ${inforgreffeResult.timestamp ? new Date(inforgreffeResult.timestamp).toLocaleString('fr-FR') : 'Non disponible'}</p>
|
|
<p><strong>Durée de la recherche:</strong> ${inforgreffeResult.duration || 0}ms</p>
|
|
<p><em>Ce rapport a été généré automatiquement par 4NK IA Front. Les informations sont fournies à titre indicatif.</em></p>
|
|
</div>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
/**
|
|
* Génère le contenu HTML pour le PDF d'une adresse
|
|
* @param {Object} addressData - Données de l'adresse
|
|
* @param {Object} geoResult - Résultat géographique
|
|
* @returns {string} Contenu HTML
|
|
*/
|
|
function generateAddressPdfContent(addressData, geoResult) {
|
|
const geocode = geoResult.geocode || {}
|
|
const risks = geoResult.risks || []
|
|
const cadastre = geoResult.cadastre || []
|
|
|
|
return `
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Rapport Adresse - ${addressData.street || 'Adresse'}</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 20px; line-height: 1.6; }
|
|
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
|
|
.title { color: #333; margin: 0; }
|
|
.subtitle { color: #666; margin: 5px 0 0 0; }
|
|
.section { margin: 20px 0; }
|
|
.section h2 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 5px; }
|
|
.info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
|
.info-item { background: #f9f9f9; padding: 15px; border-radius: 5px; }
|
|
.info-label { font-weight: bold; color: #555; }
|
|
.info-value { margin-top: 5px; }
|
|
.status-ok { color: #27ae60; font-weight: bold; }
|
|
.status-error { color: #e74c3c; font-weight: bold; }
|
|
.risk-item { background: #fff; border: 1px solid #ddd; padding: 10px; margin: 5px 0; border-radius: 5px; }
|
|
.risk-high { border-left: 4px solid #e74c3c; }
|
|
.risk-medium { border-left: 4px solid #f39c12; }
|
|
.risk-low { border-left: 4px solid #27ae60; }
|
|
.parcelle-item { background: #fff; border: 1px solid #ddd; padding: 10px; margin: 5px 0; border-radius: 5px; }
|
|
.footer { margin-top: 30px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 0.9em; }
|
|
.no-data { text-align: center; color: #666; font-style: italic; padding: 20px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1 class="title">Rapport Adresse</h1>
|
|
<p class="subtitle">Généré le ${new Date().toLocaleDateString('fr-FR')} à ${new Date().toLocaleTimeString('fr-FR')}</p>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Adresse analysée</h2>
|
|
<div class="info-item">
|
|
<div class="info-label">Adresse complète</div>
|
|
<div class="info-value">${addressData.street || ''} ${addressData.postalCode || ''} ${addressData.city || ''} ${addressData.country || ''}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Géocodage</h2>
|
|
<div class="info-grid">
|
|
<div class="info-item">
|
|
<div class="info-label">Statut</div>
|
|
<div class="info-value class="status-${geocode.success ? 'ok' : 'error'}">${geocode.success ? '✅ Géocodé' : '❌ Échec'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Score de confiance</div>
|
|
<div class="info-value">${geocode.score ? (geocode.score * 100).toFixed(0) + '%' : 'N/A'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Coordonnées</div>
|
|
<div class="info-value">${geocode.lat && geocode.lon ? `${geocode.lat}, ${geocode.lon}` : 'Non disponibles'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Adresse normalisée</div>
|
|
<div class="info-value">${geocode.label || 'Non disponible'}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Risques majeurs (${risks.length})</h2>
|
|
${risks.length === 0 ?
|
|
'<div class="no-data">✅ Aucun risque majeur identifié dans un rayon de 1km</div>' :
|
|
risks.map(risk => `
|
|
<div class="risk-item risk-${risk.level?.toLowerCase() || 'low'}">
|
|
<h3 style="margin: 0;">${risk.type}</h3>
|
|
<p><strong>Niveau:</strong> ${risk.level}</p>
|
|
<p><strong>Description:</strong> ${risk.description}</p>
|
|
${risk.distance ? `<p><strong>Distance:</strong> ${risk.distance}m</p>` : ''}
|
|
<p><strong>Source:</strong> <a href="${risk.sourceUrl}" target="_blank">GéoRisque</a></p>
|
|
</div>
|
|
`).join('')
|
|
}
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Informations cadastrales (${cadastre.length})</h2>
|
|
${cadastre.length === 0 ?
|
|
'<div class="no-data">Aucune parcelle cadastrale trouvée</div>' :
|
|
cadastre.map(parcelle => `
|
|
<div class="parcelle-item">
|
|
<h3 style="margin: 0;">Parcelle ${parcelle.section}${parcelle.numero}</h3>
|
|
<p><strong>Commune:</strong> ${parcelle.commune}</p>
|
|
${parcelle.surface ? `<p><strong>Surface:</strong> ${parcelle.surface}m²</p>` : ''}
|
|
<p><strong>Source:</strong> <a href="${parcelle.sourceUrl}" target="_blank">Cadastre</a></p>
|
|
</div>
|
|
`).join('')
|
|
}
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Sources consultées</h2>
|
|
<div class="info-grid">
|
|
<div class="info-item">
|
|
<div class="info-label">Base Adresse Nationale</div>
|
|
<div class="info-value class="status-${geocode.success ? 'ok' : 'error'}">${geocode.success ? '✅ Consultée' : '❌ Non accessible'}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">GéoRisque</div>
|
|
<div class="info-value class="status-${risks.length >= 0 ? 'ok' : 'error'}">✅ Consulté</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label">Cadastre</div>
|
|
<div class="info-value class="status-${cadastre.length >= 0 ? 'ok' : 'error'}">✅ Consulté</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
<p><strong>Recherche effectuée le:</strong> ${geoResult.timestamp ? new Date(geoResult.timestamp).toLocaleString('fr-FR') : 'Non disponible'}</p>
|
|
<p><strong>Sources:</strong> ${(geoResult.sources || []).join(', ')}</p>
|
|
<p><em>Ce rapport a été généré automatiquement par 4NK IA Front. Les informations sont fournies à titre indicatif.</em></p>
|
|
</div>
|
|
</body>
|
|
</html>`;
|
|
}
|
|
|
|
module.exports = {
|
|
generatePersonPdf,
|
|
generateCompanyPdf,
|
|
generateAddressPdf
|
|
};
|