feat: Version 3.3.5 - Optimisations pages, améliorations ergonomie et affichages dynamiques stats

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
pierre
2025-10-06 15:32:32 +02:00
parent 570a1fa1f0
commit 21657a3820
31 changed files with 1982 additions and 1442 deletions

View File

@@ -63,18 +63,10 @@ class AppConfig {
'mapbox' => [
'api_key' => '', // À remplir avec la clé API Mapbox
],
'stripe' => [
'public_key_test' => 'pk_test_51QwoVN00pblGEgsXkf8qlXmLGEpxDQcG0KLRpjrGLjJHd7AVZ4Iwd6ChgdjO0w0n3vRqwNCEW8KnHUe5eh3uIlkV00k07kCBmd', // À remplacer par votre clé publique TEST
'secret_key_test' => 'sk_test_51QwoVN00pblGEgsXnvqi8qfYpzHtesWWclvK3lzQjPNoHY0dIyOpJmxIkoLqsbmRMEUZpKS5MQ7iFDRlSqVyTo9c006yWetbsd', // À remplacer par votre clé secrète TEST
'public_key_live' => 'pk_live_XXXXXXXXXXXX', // À remplacer par votre clé publique LIVE
'secret_key_live' => 'sk_live_XXXXXXXXXXXX', // À remplacer par votre clé secrète LIVE
'webhook_secret_test' => 'whsec_test_XXXXXXXXXXXX', // À remplacer après création webhook TEST
'webhook_secret_live' => 'whsec_live_XXXXXXXXXXXX', // À remplacer après création webhook LIVE
'api_version' => '2024-06-20',
'application_fee_percent' => 0, // Pas de commission plateforme
'application_fee_minimum' => 0, // Pas de commission minimum
'mode' => 'test', // 'test' ou 'live'
],
// NOTE : La configuration Stripe est définie par environnement (voir plus bas)
// - DEV : Clés TEST Pierre (développement)
// - REC : Clés TEST Client (recette)
// - PROD : Clés LIVE Client (production)
'sms' => [
'provider' => 'ovh', // Comme mentionné dans le cahier des charges
'api_key' => '', // À remplir avec la clé API SMS OVH
@@ -103,6 +95,19 @@ class AppConfig {
'username' => 'adr_geo_user',
'password' => 'd66,AdrGeo.User',
],
// Configuration Stripe PRODUCTION - Clés LIVE du CLIENT
'stripe' => [
'public_key_test' => 'pk_test_XXXXXX', // Non utilisé en PROD
'secret_key_test' => 'sk_test_XXXXXX', // Non utilisé en PROD
'public_key_live' => 'CLIENT_PK_LIVE_A_REMPLACER', // ← À REMPLACER avec pk_live_...
'secret_key_live' => 'CLIENT_SK_LIVE_A_REMPLACER', // ← À REMPLACER avec sk_live_...
'webhook_secret_test' => 'whsec_test_XXXXXXXXXXXX',
'webhook_secret_live' => 'whsec_live_XXXXXXXXXXXX',
'api_version' => '2024-06-20',
'application_fee_percent' => 0,
'application_fee_minimum' => 0,
'mode' => 'live', // ← MODE LIVE pour la production
],
]);
// Configuration RECETTE
@@ -127,7 +132,19 @@ class AppConfig {
'username' => 'adr_geo_user',
'password' => 'd66,AdrGeoRec.User',
],
// Vous pouvez remplacer d'autres paramètres spécifiques à l'environnement de recette ici
// Configuration Stripe RECETTE - Clés TEST du CLIENT
'stripe' => [
'public_key_test' => 'pk_test_51S5oMd1tQE0jBEomd1u28D1bUujOcl87ASuGf9xulcz4rY27QfHrLBtQj20MVlWta4AGXsX0YMfeOJFE66AlGlkz00vG30U8Rr',
'secret_key_test' => 'sk_test_51S5oMd1tQE0jBEomAhzPBvUcCf0HX9ydK0xq7DagKnidp3JsovbQoVaTj24TKSUPvujQA3PP7IpIS8iWzAd15Rte00TETmbimh',
'public_key_live' => 'pk_live_XXXXXX', // Non utilisé en REC
'secret_key_live' => 'sk_live_XXXXXX', // Non utilisé en REC
'webhook_secret_test' => 'whsec_test_XXXXXXXXXXXX',
'webhook_secret_live' => 'whsec_live_XXXXXXXXXXXX',
'api_version' => '2024-06-20',
'application_fee_percent' => 0,
'application_fee_minimum' => 0,
'mode' => 'test', // ← MODE TEST pour la recette
],
]);
// Configuration DÉVELOPPEMENT
@@ -152,6 +169,19 @@ class AppConfig {
'username' => 'adr_geo_user',
'password' => 'd66,AdrGeoDev.User',
],
// Configuration Stripe DÉVELOPPEMENT - Clés TEST de Pierre (plateforme de test existante)
'stripe' => [
'public_key_test' => 'pk_test_51QwoVN00pblGEgsXkf8qlXmLGEpxDQcG0KLRpjrGLjJHd7AVZ4Iwd6ChgdjO0w0n3vRqwNCEW8KnHUe5eh3uIlkV00k07kCBmd',
'secret_key_test' => 'sk_test_51QwoVN00pblGEgsXnvqi8qfYpzHtesWWclvK3lzQjPNoHY0dIyOpJmxIkoLqsbmRMEUZpKS5MQ7iFDRlSqVyTo9c006yWetbsd',
'public_key_live' => 'pk_live_XXXXXX', // Non utilisé en DEV
'secret_key_live' => 'sk_live_XXXXXX', // Non utilisé en DEV
'webhook_secret_test' => 'whsec_test_XXXXXXXXXXXX',
'webhook_secret_live' => 'whsec_live_XXXXXXXXXXXX',
'api_version' => '2024-06-20',
'application_fee_percent' => 0,
'application_fee_minimum' => 0,
'mode' => 'test', // ← MODE TEST pour le développement
],
// Vous pouvez activer des fonctionnalités de débogage en développement
'debug' => true,
]);

View File

@@ -576,6 +576,43 @@ class PassageController {
'operationId' => $operationId
]);
// Enregistrer la génération du reçu dans shutdown_function pour garantir son exécution
// Même si le worker FPM est tué après fastcgi_finish_request()
$fkType = isset($data['fk_type']) ? (int)$data['fk_type'] : 0;
if ($fkType === 1 || $fkType === 5) {
// Vérifier si un email a été fourni
$hasEmail = false;
if (!empty($data['email'])) {
$hasEmail = filter_var($data['email'], FILTER_VALIDATE_EMAIL) !== false;
} elseif (!empty($encryptedEmail)) {
$hasEmail = true;
}
if ($hasEmail) {
$capturedPassageId = $passageId; // Capturer pour la closure
register_shutdown_function(function() use ($capturedPassageId) {
try {
$receiptService = new \App\Services\ReceiptService();
$receiptGenerated = $receiptService->generateReceiptForPassage($capturedPassageId);
if ($receiptGenerated) {
LogService::log('Reçu généré automatiquement pour le passage', [
'level' => 'info',
'passageId' => $capturedPassageId
]);
}
} catch (Exception $e) {
LogService::log('Erreur lors de la génération automatique du reçu', [
'level' => 'warning',
'error' => $e->getMessage(),
'passageId' => $capturedPassageId
]);
}
});
}
}
// Envoyer la réponse immédiatement pour éviter les timeouts
Response::json([
'status' => 'success',
@@ -583,51 +620,19 @@ class PassageController {
'passage_id' => $passageId,
'receipt_generated' => false // On va générer le reçu en arrière-plan
], 201);
// Flush la sortie pour s'assurer que la réponse est envoyée
if (ob_get_level()) {
ob_end_flush();
}
flush();
// Fermer la connexion HTTP mais continuer le traitement
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
// Générer automatiquement un reçu si c'est un don (fk_type = 1) avec email valide
if (isset($data['fk_type']) && (int)$data['fk_type'] === 1) {
// Vérifier si un email a été fourni
$hasEmail = false;
if (!empty($data['email'])) {
$hasEmail = filter_var($data['email'], FILTER_VALIDATE_EMAIL) !== false;
} elseif (!empty($encryptedEmail)) {
// L'email a déjà été validé lors du chiffrement
$hasEmail = true;
}
if ($hasEmail) {
try {
$receiptService = new \App\Services\ReceiptService();
$receiptGenerated = $receiptService->generateReceiptForPassage($passageId);
if ($receiptGenerated) {
LogService::log('Reçu généré automatiquement pour le passage', [
'level' => 'info',
'passageId' => $passageId
]);
}
} catch (Exception $e) {
LogService::log('Erreur lors de la génération automatique du reçu', [
'level' => 'warning',
'error' => $e->getMessage(),
'passageId' => $passageId
]);
}
}
}
return; // Fin de la méthode, éviter d'exécuter le code après
return; // Fin de la méthode
} catch (Exception $e) {
LogService::log('Erreur lors de la création du passage', [
'level' => 'error',
@@ -792,65 +797,72 @@ class PassageController {
'passageId' => $passageId
]);
// Enregistrer la génération du reçu dans shutdown_function pour garantir son exécution
// Même si le worker FPM est tué après fastcgi_finish_request()
$capturedPassageId = $passageId;
$capturedDb = $this->db;
register_shutdown_function(function() use ($capturedPassageId, $capturedDb) {
try {
// Récupérer les données actualisées du passage
$stmt = $capturedDb->prepare('SELECT fk_type, encrypted_email, nom_recu FROM ope_pass WHERE id = ?');
$stmt->execute([$capturedPassageId]);
$updatedPassage = $stmt->fetch(PDO::FETCH_ASSOC);
if ($updatedPassage) {
// Générer un reçu si :
// - C'est un don (fk_type = 1) ou un lot (fk_type = 5)
// - Il y a un email valide
// - Il n'y a pas encore de reçu (nom_recu est vide ou null)
$fkType = (int)$updatedPassage['fk_type'];
if (($fkType === 1 || $fkType === 5) &&
!empty($updatedPassage['encrypted_email']) &&
empty($updatedPassage['nom_recu'])) {
// Vérifier que l'email est valide en le déchiffrant
$email = ApiService::decryptSearchableData($updatedPassage['encrypted_email']);
if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
$receiptService = new \App\Services\ReceiptService();
$receiptGenerated = $receiptService->generateReceiptForPassage($capturedPassageId);
if ($receiptGenerated) {
LogService::log('Reçu généré automatiquement après mise à jour du passage', [
'level' => 'info',
'passageId' => $capturedPassageId
]);
}
}
}
}
} catch (Exception $e) {
LogService::log('Erreur lors de la génération automatique du reçu après mise à jour', [
'level' => 'warning',
'error' => $e->getMessage(),
'passageId' => $capturedPassageId
]);
}
});
// Envoyer la réponse immédiatement pour éviter les timeouts
Response::json([
'status' => 'success',
'message' => 'Passage mis à jour avec succès',
'receipt_generated' => false // On va générer le reçu en arrière-plan
], 200);
// Flush la sortie pour s'assurer que la réponse est envoyée
if (ob_get_level()) {
ob_end_flush();
}
flush();
// Fermer la connexion HTTP mais continuer le traitement
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
}
// Maintenant générer le reçu en arrière-plan après avoir envoyé la réponse
try {
// Récupérer les données actualisées du passage
$stmt = $this->db->prepare('SELECT fk_type, encrypted_email, nom_recu FROM ope_pass WHERE id = ?');
$stmt->execute([$passageId]);
$updatedPassage = $stmt->fetch(PDO::FETCH_ASSOC);
if ($updatedPassage) {
// Générer un reçu si :
// - C'est un don (fk_type = 1)
// - Il y a un email valide
// - Il n'y a pas encore de reçu (nom_recu est vide ou null)
if ((int)$updatedPassage['fk_type'] === 1 &&
!empty($updatedPassage['encrypted_email']) &&
empty($updatedPassage['nom_recu'])) {
// Vérifier que l'email est valide en le déchiffrant
$email = ApiService::decryptSearchableData($updatedPassage['encrypted_email']);
if (!empty($email) && filter_var($email, FILTER_VALIDATE_EMAIL)) {
$receiptService = new \App\Services\ReceiptService();
$receiptGenerated = $receiptService->generateReceiptForPassage($passageId);
if ($receiptGenerated) {
LogService::log('Reçu généré automatiquement après mise à jour du passage', [
'level' => 'info',
'passageId' => $passageId
]);
}
}
}
}
} catch (Exception $e) {
LogService::log('Erreur lors de la génération automatique du reçu après mise à jour', [
'level' => 'warning',
'error' => $e->getMessage(),
'passageId' => $passageId
]);
}
return; // Fin de la méthode, éviter d'exécuter le code après
return; // Fin de la méthode
} catch (Exception $e) {
LogService::log('Erreur lors de la mise à jour du passage', [
'level' => 'error',

View File

@@ -51,9 +51,10 @@ class ReceiptService {
return false;
}
// Vérifier que c'est un don effectué (fk_type = 1) avec email valide
if ((int)$passageData['fk_type'] !== 1) {
return false; // Pas un don, pas de reçu
// Vérifier que c'est un don effectué (fk_type = 1) ou un lot (fk_type = 5) avec email valide
$fkType = (int)$passageData['fk_type'];
if ($fkType !== 1 && $fkType !== 5) {
return false; // Ni don ni lot, pas de reçu
}
// Déchiffrer et vérifier l'email