From b3d4064841a5113dc35840fc027feb4904935d9b Mon Sep 17 00:00:00 2001 From: Tykayn Date: Fri, 27 Jun 2025 00:35:11 +0200 Subject: [PATCH] add logging data for edit form --- src/Controller/AdminController.php | 453 +++++++++++++++-------------- 1 file changed, 229 insertions(+), 224 deletions(-) diff --git a/src/Controller/AdminController.php b/src/Controller/AdminController.php index d0a4f55..d00174e 100644 --- a/src/Controller/AdminController.php +++ b/src/Controller/AdminController.php @@ -1,6 +1,6 @@ actionLogger->log('labourer_toutes_les_zones', []); - $updateExisting =true; - $stats_all = $this->entityManager->getRepository(Stats::class)->findAll(); + $this->actionLogger->log('labourer_toutes_les_zones', []); + $updateExisting = true; + + $stats_all = $this->entityManager->getRepository(Stats::class)->findAll(); echo 'on a trouvé ' . count($stats_all) . ' zones à labourer
'; - - foreach($stats_all as $stats) { - echo '
on laboure la zone '.$stats->getZone() . ' '; + foreach ($stats_all as $stats) { + + echo '
on laboure la zone ' . $stats->getZone() . ' '; $processedCount = 0; $updatedCount = 0; $insee_code = $stats->getZone(); // Vérifier si le code INSEE est un nombre valide - // Vérifier si les stats ont été modifiées il y a moins de 24h - if ($stats->getDateModified() !== null) { - $now = new \DateTime(); - $diff = $now->diff($stats->getDateModified()); - $hours = $diff->h + ($diff->days * 24); - - if ($hours < 24) { - echo 'Stats modifiées il y a moins de 24h - on passe au suivant
'; - continue; - } - } - if (!is_numeric($insee_code) || $insee_code == 'undefined' || $insee_code == '') { - echo 'Code INSEE invalide : ' . $insee_code . ' - on passe au suivant
'; + // Vérifier si les stats ont été modifiées il y a moins de 24h + if ($stats->getDateModified() !== null) { + $now = new \DateTime(); + $diff = $now->diff($stats->getDateModified()); + $hours = $diff->h + ($diff->days * 24); + + if ($hours < 24) { + echo 'Stats modifiées il y a moins de 24h - on passe au suivant
'; continue; } + } + if (!is_numeric($insee_code) || $insee_code == 'undefined' || $insee_code == '') { + echo 'Code INSEE invalide : ' . $insee_code . ' - on passe au suivant
'; + continue; + } $places_overpass = $this->motocultrice->labourer($stats->getZone()); $places = $places_overpass; foreach ($places as $placeData) { - + // Vérifier si le lieu existe déjà $existingPlace = $this->entityManager->getRepository(Place::class) ->findOneBy(['osmId' => $placeData['id']]); - + if (!$existingPlace) { $place = new Place(); $place->setOsmId($placeData['id']) - ->setOsmKind($placeData['type']) - ->setZipCode($insee_code) - ->setUuidForUrl($this->motocultrice->uuid_create()) - ->setModifiedDate(new \DateTime()) - ->setStats($stats) - ->setDead(false) - ->setOptedOut(false) - ->setMainTag($this->motocultrice->find_main_tag($placeData['tags']) ?? '') - ->setStreet($this->motocultrice->find_street($placeData['tags']) ?? '') - ->setHousenumber($this->motocultrice->find_housenumber($placeData['tags']) ?? '') - ->setSiret($this->motocultrice->find_siret($placeData['tags']) ?? '') - ->setAskedHumainsSupport(false) - ->setLastContactAttemptDate(null) - ->setNote($this->motocultrice->find_tag($placeData['tags'], 'note') ? true : false) - ->setNoteContent($this->motocultrice->find_tag($placeData['tags'], 'note') ?? '') - ->setPlaceCount(0) + ->setOsmKind($placeData['type']) + ->setZipCode($insee_code) + ->setUuidForUrl($this->motocultrice->uuid_create()) + ->setModifiedDate(new \DateTime()) + ->setStats($stats) + ->setDead(false) + ->setOptedOut(false) + ->setMainTag($this->motocultrice->find_main_tag($placeData['tags']) ?? '') + ->setStreet($this->motocultrice->find_street($placeData['tags']) ?? '') + ->setHousenumber($this->motocultrice->find_housenumber($placeData['tags']) ?? '') + ->setSiret($this->motocultrice->find_siret($placeData['tags']) ?? '') + ->setAskedHumainsSupport(false) + ->setLastContactAttemptDate(null) + ->setNote($this->motocultrice->find_tag($placeData['tags'], 'note') ? true : false) + ->setNoteContent($this->motocultrice->find_tag($placeData['tags'], 'note') ?? '') + ->setPlaceCount(0) // ->setOsmData($placeData['modified'] ?? null) - ; - + ; + // Mettre à jour les données depuis Overpass $place->update_place_from_overpass_data($placeData); - + $this->entityManager->persist($place); $stats->addPlace($place); $processedCount++; @@ -118,20 +117,20 @@ final class AdminController extends AbstractController $this->entityManager->persist($existingPlace); $updatedCount++; } - } + } // mettre à jour les stats // Récupérer tous les commerces de la zone $commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]); // Récupérer les stats existantes pour la zone $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); - if(!$stats) { + if (!$stats) { $stats = new Stats(); $stats->setZone($insee_code); } - $urls = $stats->getAllCTCUrlsMap(); - + $urls = $stats->getAllCTCUrlsMap(); + $statsHistory = $this->entityManager->getRepository(StatsHistory::class) ->createQueryBuilder('sh') ->where('sh.stats = :stats') @@ -140,7 +139,7 @@ final class AdminController extends AbstractController ->setMaxResults(365) ->getQuery() ->getResult(); - + // Calculer les statistiques $calculatedStats = $this->motocultrice->calculateStats($commerces); @@ -151,7 +150,7 @@ final class AdminController extends AbstractController $stats->setAvecSite($calculatedStats['counters']['avec_site']); $stats->setAvecAccessibilite($calculatedStats['counters']['avec_accessibilite']); $stats->setAvecNote($calculatedStats['counters']['avec_note']); - + $stats->setCompletionPercent($calculatedStats['completion_percent']); // Associer les stats à chaque commerce @@ -169,22 +168,22 @@ final class AdminController extends AbstractController $timestamps[] = $place->getOsmDataDate()->getTimestamp(); } } - + if (!empty($timestamps)) { // Date la plus ancienne (min) $minTimestamp = min($timestamps); $stats->setOsmDataDateMin(new \DateTime('@' . $minTimestamp)); - + // Date la plus récente (max) $maxTimestamp = max($timestamps); $stats->setOsmDataDateMax(new \DateTime('@' . $maxTimestamp)); - + // Date moyenne $avgTimestamp = array_sum($timestamps) / count($timestamps); $stats->setOsmDataDateAvg(new \DateTime('@' . (int)$avgTimestamp)); } - if($stats->getDateCreated() == null) { + if ($stats->getDateCreated() == null) { $stats->setDateCreated(new \DateTime()); } @@ -193,7 +192,7 @@ final class AdminController extends AbstractController // Créer un historique des statistiques $statsHistory = new StatsHistory(); $statsHistory->setDate(new \DateTime()) - ->setStats($stats); + ->setStats($stats); // Compter les Places avec email et SIRET $placesWithEmail = 0; @@ -208,34 +207,33 @@ final class AdminController extends AbstractController } $statsHistory->setPlacesCount($stats->getPlaces()->count()) - ->setOpeningHoursCount($stats->getAvecHoraires()) - ->setAddressCount($stats->getAvecAdresse()) - ->setWebsiteCount($stats->getAvecSite()) - ->setSiretCount($placesWithSiret) - ->setEmailsCount($placesWithEmail) - ->setCompletionPercent($stats->getCompletionPercent()) - ->setStats($stats); - + ->setOpeningHoursCount($stats->getAvecHoraires()) + ->setAddressCount($stats->getAvecAdresse()) + ->setWebsiteCount($stats->getAvecSite()) + ->setSiretCount($placesWithSiret) + ->setEmailsCount($placesWithEmail) + ->setCompletionPercent($stats->getCompletionPercent()) + ->setStats($stats); + $this->entityManager->persist($statsHistory); - + $this->entityManager->persist($stats); $this->entityManager->flush(); - + $message = 'Labourage terminé avec succès. ' . $processedCount . ' nouveaux lieux traités.'; if ($updateExisting) { - $message .= ' ' . $updatedCount . ' lieux existants mis à jour pour la zone '.$stats->getName().' ('.$stats->getZone().').'; + $message .= ' ' . $updatedCount . ' lieux existants mis à jour pour la zone ' . $stats->getName() . ' (' . $stats->getZone() . ').'; } $this->addFlash('success', $message); } - + $this->entityManager->flush(); - + $this->entityManager->flush(); $this->addFlash('success', 'Labourage des ' . count($stats_all) . ' zones terminé avec succès.'); return $this->redirectToRoute('app_public_dashboard'); - } #[Route('/admin', name: 'app_admin')] @@ -249,7 +247,7 @@ final class AdminController extends AbstractController #[Route('/admin/stats/{insee_code}', name: 'app_admin_stats')] public function calculer_stats(string $insee_code): Response { - + // Récupérer les stats existantes pour la zone $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); if (!$stats) { @@ -257,11 +255,11 @@ final class AdminController extends AbstractController return $this->redirectToRoute('app_admin_labourer', ['insee_code' => $insee_code]); } $commerces = $stats->getPlaces(); - + $this->actionLogger->log('stats_de_ville', ['insee_code' => $insee_code, 'nom' => $stats->getZone()]); // Récupérer tous les commerces de la zone // $commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code, 'dead' => false]); - if(!$stats) { + if (!$stats) { // Si aucune stat n'existe, on en crée une vide pour éviter les erreurs, mais sans la sauvegarder $stats = new Stats(); $stats->setZone($insee_code); @@ -276,8 +274,8 @@ final class AdminController extends AbstractController ->orderBy('sh.id', 'DESC') ->setMaxResults(100) ->getQuery() - ->getResult(); - + ->getResult(); + // Données pour le graphique des modifications par trimestre $modificationsByQuarter = []; foreach ($commerces as $commerce) { @@ -335,12 +333,18 @@ final class AdminController extends AbstractController #[Route('/admin/placeType/{osm_kind}/{osm_id}', name: 'app_admin_by_osm_id')] public function placeType(string $osm_kind, string $osm_id): Response { - $this->actionLogger->log('admin/placeType', ['osm_kind' => $osm_kind, 'osm_id' => $osm_id]); + $place = $this->entityManager->getRepository(Place::class)->findOneBy(['osm_kind' => $osm_kind, 'osmId' => $osm_id]); - if($place) { + if ($place) { + $this->actionLogger->log('ERROR_admin/placeType', ['osm_kind' => $osm_kind, 'osm_id' => $osm_id, + 'name' => $place->getName(), + 'code_insee' => $place->getZipCode(), + 'uuid' => $place->getUuidForUrl() + ]); return $this->redirectToRoute('app_admin_commerce', ['id' => $place->getId()]); } else { $this->addFlash('error', 'Le lieu n\'existe pas.'); + $this->actionLogger->log('ERROR_admin/placeType', ['osm_kind' => $osm_kind, 'osm_id' => $osm_id]); return $this->redirectToRoute('app_public_index'); } } @@ -354,8 +358,7 @@ final class AdminController extends AbstractController public function commerce(int $id): Response { - $this->actionLogger->log('admin_show_commerce_form_id', ['id' => $id]); - + // Vérifier si on est en prod if ($this->getParameter('kernel.environment') === 'prod') { $this->addFlash('error', 'Vous n\'avez pas accès à cette page en production.'); @@ -365,12 +368,18 @@ final class AdminController extends AbstractController if (!$commerce) { throw $this->createNotFoundException('Commerce non trouvé'); + $this->actionLogger->log('ERROR_admin_show_commerce_form_id', ['id' => $id]); } - + $this->actionLogger->log('ERROR_admin_show_commerce_form_id', [ + 'id' => $id, + 'name' => $commerce->getName(), + 'code_insee' => $commerce->getZipCode(), + 'uuid' => $commerce->getUuidForUrl() + ]); // Redirection vers la page de modification avec les paramètres nécessaires return $this->redirectToRoute('app_public_edit', [ 'zipcode' => $commerce->getZipCode(), - 'name' => $commerce->getName()!='' ? $commerce->getName() : '?', + 'name' => $commerce->getName() != '' ? $commerce->getName() : '?', 'uuid' => $commerce->getUuidForUrl() ]); } @@ -402,13 +411,13 @@ final class AdminController extends AbstractController $stats->setDateCreated(new \DateTime()); $stats->setDateModified(new \DateTime()); $stats->setZone($insee_code) - ->setPlacesCount(0) - ->setAvecHoraires(0) - ->setAvecAdresse(0) - ->setAvecSite(0) - ->setAvecAccessibilite(0) - ->setAvecNote(0) - ->setCompletionPercent(0); + ->setPlacesCount(0) + ->setAvecHoraires(0) + ->setAvecAdresse(0) + ->setAvecSite(0) + ->setAvecAccessibilite(0) + ->setAvecNote(0) + ->setCompletionPercent(0); $this->entityManager->persist($stats); $this->entityManager->flush(); } @@ -439,8 +448,6 @@ final class AdminController extends AbstractController $this->addFlash('error', 'Erreur lors de la récupération des données de l\'API : ' . $e->getMessage()); $this->actionLogger->log('ERROR_labourer_geoapi', ['insee_code' => $insee_code, 'message' => $e->getMessage()]); - - } // Récupérer le budget annuel via l'API des finances publiques @@ -460,14 +467,14 @@ final class AdminController extends AbstractController ->where('p.zip_code = :zip_code') ->setParameter('zip_code', $insee_code) ->getQuery(); - + $existingPlacesResult = $existingPlacesQuery->getResult(); $placesByOsmKey = []; foreach ($existingPlacesResult as $placeData) { - // var_dump($placeData); - // die( ); + // var_dump($placeData); + // die( ); // Clé unique combinant osmId ET osmKind pour éviter les conflits entre node/way $osmKey = $placeData['osm_kind'] . '_' . $placeData['osmId']; $placesByOsmKey[$osmKey] = $placeData['id']; @@ -482,7 +489,7 @@ final class AdminController extends AbstractController $overpass_osm_ids = array_map(fn($place) => $place['id'], $places_overpass); // RÉDUCTION de la taille du batch pour éviter l'explosion mémoire - $batchSize = 10000; + $batchSize = 10000; $i = 0; $notFoundOsmKeys = []; foreach ($places_overpass as $placeData) { @@ -492,24 +499,24 @@ final class AdminController extends AbstractController if (!$existingPlaceId) { $place = new Place(); $place->setOsmId($placeData['id']) - ->setOsmKind($placeData['type']) - ->setZipCode($insee_code) - ->setUuidForUrl($this->motocultrice->uuid_create()) - ->setModifiedDate(new \DateTime()) - ->setStats($stats) - ->setDead(false) - ->setOptedOut(false) - ->setMainTag($this->motocultrice->find_main_tag($placeData['tags']) ?? '') - ->setStreet($this->motocultrice->find_street($placeData['tags']) ?? '') - ->setHousenumber($this->motocultrice->find_housenumber($placeData['tags']) ?? '') - ->setSiret($this->motocultrice->find_siret($placeData['tags']) ?? '') - ->setAskedHumainsSupport(false) - ->setLastContactAttemptDate(null) - ->setNote($this->motocultrice->find_tag($placeData['tags'], 'note') ? true : false) - ->setNoteContent($this->motocultrice->find_tag($placeData['tags'], 'note') ?? '') - ->setPlaceCount(0) + ->setOsmKind($placeData['type']) + ->setZipCode($insee_code) + ->setUuidForUrl($this->motocultrice->uuid_create()) + ->setModifiedDate(new \DateTime()) + ->setStats($stats) + ->setDead(false) + ->setOptedOut(false) + ->setMainTag($this->motocultrice->find_main_tag($placeData['tags']) ?? '') + ->setStreet($this->motocultrice->find_street($placeData['tags']) ?? '') + ->setHousenumber($this->motocultrice->find_housenumber($placeData['tags']) ?? '') + ->setSiret($this->motocultrice->find_siret($placeData['tags']) ?? '') + ->setAskedHumainsSupport(false) + ->setLastContactAttemptDate(null) + ->setNote($this->motocultrice->find_tag($placeData['tags'], 'note') ? true : false) + ->setNoteContent($this->motocultrice->find_tag($placeData['tags'], 'note') ?? '') + ->setPlaceCount(0) // ->setOsmData($placeData['modified'] ?? null) - ; + ; $place->update_place_from_overpass_data($placeData); $this->entityManager->persist($place); $stats->addPlace($place); @@ -533,15 +540,15 @@ final class AdminController extends AbstractController } } $i++; - + // FLUSH/CLEAR plus fréquent pour éviter l'explosion mémoire if (($i % $batchSize) === 0) { $this->entityManager->flush(); $this->entityManager->clear(); - + // Forcer le garbage collector gc_collect_cycles(); - + // Recharger les stats après clear $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); } @@ -556,11 +563,11 @@ final class AdminController extends AbstractController $osmKey = $placeData['type'] . '_' . $placeData['id']; $overpassOsmKeys[$osmKey] = true; } - + // ÉLIMINER LES DOUBLONS dans les lieux existants avant suppression $uniquePlacesByOsmKey = []; $duplicatePlaceIds = []; - + foreach ($placesByOsmKey as $osmKey => $placeId) { if (isset($uniquePlacesByOsmKey[$osmKey])) { // Doublon détecté, garder le plus ancien (ID le plus petit) @@ -574,7 +581,7 @@ final class AdminController extends AbstractController $uniquePlacesByOsmKey[$osmKey] = $placeId; } } - + // Supprimer les doublons détectés if (!empty($duplicatePlaceIds)) { $duplicateDeleteQuery = $this->entityManager->createQuery( @@ -583,7 +590,7 @@ final class AdminController extends AbstractController $duplicateDeleteQuery->setParameter('placeIds', $duplicatePlaceIds); $duplicateDeletedCount = $duplicateDeleteQuery->execute(); } - + // Trouver les lieux existants uniques qui ne sont plus dans overpass $placesToDelete = []; foreach ($uniquePlacesByOsmKey as $osmKey => $placeId) { @@ -591,7 +598,7 @@ final class AdminController extends AbstractController $placesToDelete[] = $placeId; } } - + // Supprimer les lieux non trouvés dans overpass en une seule requête if (!empty($placesToDelete)) { $deleteQuery = $this->entityManager->createQuery( @@ -605,10 +612,10 @@ final class AdminController extends AbstractController // Flush final $this->entityManager->flush(); $this->entityManager->clear(); - + // NETTOYAGE D'UNICITÉ des Places après le clear pour éliminer les doublons persistants // Approche en deux étapes pour éviter l'erreur MySQL "target table for update in FROM clause" - + // Étape 1 : Identifier les doublons $duplicateIdsQuery = $this->entityManager->createQuery( 'SELECT p.id FROM App\Entity\Place p @@ -619,7 +626,7 @@ final class AdminController extends AbstractController )' ); $duplicateIds = $duplicateIdsQuery->getResult(); - + // Étape 2 : Supprimer les doublons identifiés if (!empty($duplicateIds)) { $duplicateIds = array_column($duplicateIds, 'id'); @@ -631,19 +638,19 @@ final class AdminController extends AbstractController } else { $duplicateCleanupCount = 0; } - + // Récupérer tous les commerces de la zone qui n'ont pas été supprimés $commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]); // Récupérer les stats existantes pour la zone $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); - if(!$stats) { + if (!$stats) { $stats = new Stats(); $stats->setZone($insee_code); } - $urls = $stats->getAllCTCUrlsMap(); - + $urls = $stats->getAllCTCUrlsMap(); + $statsHistory = $this->entityManager->getRepository(StatsHistory::class) ->createQueryBuilder('sh') ->where('sh.stats = :stats') @@ -652,7 +659,7 @@ final class AdminController extends AbstractController ->setMaxResults(365) ->getQuery() ->getResult(); - + // Calculer les statistiques $calculatedStats = $this->motocultrice->calculateStats($commerces); @@ -680,22 +687,22 @@ final class AdminController extends AbstractController $timestamps[] = $place->getOsmDataDate()->getTimestamp(); } } - + if (!empty($timestamps)) { // Date la plus ancienne (min) $minTimestamp = min($timestamps); $stats->setOsmDataDateMin(new \DateTime('@' . $minTimestamp)); - + // Date la plus récente (max) $maxTimestamp = max($timestamps); $stats->setOsmDataDateMax(new \DateTime('@' . $maxTimestamp)); - + // Date moyenne $avgTimestamp = array_sum($timestamps) / count($timestamps); $stats->setOsmDataDateAvg(new \DateTime('@' . (int)$avgTimestamp)); } - if($stats->getDateCreated() == null) { + if ($stats->getDateCreated() == null) { $stats->setDateCreated(new \DateTime()); } @@ -704,7 +711,7 @@ final class AdminController extends AbstractController // Créer un historique des statistiques $statsHistory = new StatsHistory(); $statsHistory->setDate(new \DateTime()) - ->setStats($stats); + ->setStats($stats); // Compter les Places avec email et SIRET $placesWithEmail = 0; @@ -721,25 +728,24 @@ final class AdminController extends AbstractController if ($place->getName() && $place->getName() !== '') { $placesWithName++; } - } $statsHistory->setPlacesCount($stats->getPlaces()->count()) - ->setOpeningHoursCount($stats->getAvecHoraires()) - ->setAddressCount($stats->getAvecAdresse()) - ->setWebsiteCount($stats->getAvecSite()) - ->setNamesCount($placesWithName) - ->setSiretCount($placesWithSiret) - ->setEmailsCount($placesWithEmail) - ->setCompletionPercent($stats->getCompletionPercent()) - ->setStats($stats); - + ->setOpeningHoursCount($stats->getAvecHoraires()) + ->setAddressCount($stats->getAvecAdresse()) + ->setWebsiteCount($stats->getAvecSite()) + ->setNamesCount($placesWithName) + ->setSiretCount($placesWithSiret) + ->setEmailsCount($placesWithEmail) + ->setCompletionPercent($stats->getCompletionPercent()) + ->setStats($stats); + $this->entityManager->persist($statsHistory); - + $this->entityManager->persist($stats); $this->entityManager->flush(); - + $message = 'Labourage terminé avec succès. ' . $processedCount . ' nouveaux lieux traités.'; if ($updateExisting) { $message .= ' ' . $updatedCount . ' lieux existants mis à jour.'; @@ -747,7 +753,7 @@ final class AdminController extends AbstractController if ($deletedCount > 0) { $message .= ' ' . $deletedCount . ' lieux ont été supprimés.'; } - $message .= ' Zone : '.$stats->getName().' ('.$stats->getZone().').'; + $message .= ' Zone : ' . $stats->getName() . ' (' . $stats->getZone() . ').'; $this->addFlash('success', $message); // Afficher le log des objets non trouvés à la fin @@ -760,7 +766,7 @@ final class AdminController extends AbstractController $this->addFlash('error', 'Erreur lors du labourage : ' . $e->getMessage()); die(var_dump($e)); } - + // return $this->redirectToRoute('app_public_dashboard'); return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]); } @@ -770,11 +776,11 @@ final class AdminController extends AbstractController { $this->actionLogger->log('admin/delete_place', ['id' => $id]); $commerce = $this->entityManager->getRepository(Place::class)->find($id); - if($commerce) { + if ($commerce) { $this->entityManager->remove($commerce); - $this->entityManager->flush(); + $this->entityManager->flush(); - $this->addFlash('success', 'Le lieu '.$commerce->getName().' a été supprimé avec succès de OSM Mes commerces, mais pas dans OpenStreetMap.'); + $this->addFlash('success', 'Le lieu ' . $commerce->getName() . ' a été supprimé avec succès de OSM Mes commerces, mais pas dans OpenStreetMap.'); } else { $this->addFlash('error', 'Le lieu n\'existe pas.'); } @@ -811,7 +817,6 @@ final class AdminController extends AbstractController $this->entityManager->flush(); $this->addFlash('success', 'La zone ' . $insee_code . ' et toutes les données associées ont été supprimées avec succès.'); - } catch (\Exception $e) { $this->addFlash('error', 'Une erreur est survenue lors de la suppression de la zone ' . $insee_code . ': ' . $e->getMessage()); } @@ -825,11 +830,11 @@ final class AdminController extends AbstractController { $this->actionLogger->log('export_all_places', []); $places = $this->entityManager->getRepository(Place::class)->findAll(); - + $csvData = []; $csvData[] = [ 'Nom', - 'Email', + 'Email', 'Code postal', 'ID OSM', 'Type OSM', @@ -840,7 +845,7 @@ final class AdminController extends AbstractController 'Inactif', 'Support humain demandé', 'A des horaires', - 'A une adresse', + 'A une adresse', 'A un site web', 'A accessibilité', 'A une note' @@ -857,7 +862,7 @@ final class AdminController extends AbstractController $place->getLastContactAttemptDate() ? $place->getLastContactAttemptDate()->format('Y-m-d H:i:s') : '', $place->getNote(), $place->isOptedOut() ? 'Oui' : 'Non', - $place->isDead() ? 'Oui' : 'Non', + $place->isDead() ? 'Oui' : 'Non', $place->isAskedHumainsSupport() ? 'Oui' : 'Non', $place->hasOpeningHours() ? 'Oui' : 'Non', $place->hasAddress() ? 'Oui' : 'Non', @@ -879,22 +884,22 @@ final class AdminController extends AbstractController $response->setContent(stream_get_contents($handle)); fclose($handle); - + return $response; } #[Route('/admin/export_csv/{insee_code}', name: 'app_admin_export_csv')] public function export_csv(string $insee_code): Response - { + { $this->actionLogger->log('admin/export_csv', ['insee_code' => $insee_code]); $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); $response = new Response($this->motocultrice->export($insee_code)); $response->headers->set('Content-Type', 'text/csv'); - + $slug_name = str_replace(' ', '-', $stats->getName()); - $this->actionLogger->log('export_csv', ['insee_code'=> $insee_code, 'slug_name' => $slug_name]); + $this->actionLogger->log('export_csv', ['insee_code' => $insee_code, 'slug_name' => $slug_name]); $response->headers->set('Content-Disposition', 'attachment; filename="osm-commerces-export_' . $insee_code . '_' . $slug_name . '_' . date('Y-m-d_H-i-s') . '.csv"'); @@ -905,20 +910,20 @@ final class AdminController extends AbstractController public function make_email_for_place(Place $place): Response { $this->actionLogger->log('admin/make_email_for_place', ['insee_code' => $place->getId()]); - + return $this->render('admin/view_email_for_place.html.twig', ['place' => $place]); } #[Route('/admin/no_more_sollicitation_for_place/{id}', name: 'app_admin_no_more_sollicitation_for_place')] public function no_more_sollicitation_for_place(Place $place): Response { - $this->actionLogger->log('no_more_sollicitation_for_place', ['place_id'=> $place->getId()]); + $this->actionLogger->log('no_more_sollicitation_for_place', ['place_id' => $place->getId()]); $place->setOptedOut(true); $this->entityManager->persist($place); $this->entityManager->flush(); - $this->addFlash('success', 'Votre lieu '.$place->getName().' ne sera plus sollicité pour mettre à jour ses informations.'); + $this->addFlash('success', 'Votre lieu ' . $place->getName() . ' ne sera plus sollicité pour mettre à jour ses informations.'); return $this->redirectToRoute('app_public_index'); } @@ -926,14 +931,14 @@ final class AdminController extends AbstractController #[Route('/admin/send_email_to_place/{id}', name: 'app_admin_send_email_to_place')] public function send_email_to_place(Place $place, \Symfony\Component\Mailer\MailerInterface $mailer): Response { - $this->actionLogger->log('send_email_to_place', ['place_id'=> $place->getId()]); - + $this->actionLogger->log('send_email_to_place', ['place_id' => $place->getId()]); + // Vérifier si le lieu est opted out if ($place->isOptedOut()) { $this->addFlash('error', 'Ce lieu a demandé à ne plus être sollicité pour mettre à jour ses informations.'); - $this->actionLogger->log('could_not_send_email_to_opted_out_place', ['place_id'=> $place->getId()]); - + $this->actionLogger->log('could_not_send_email_to_opted_out_place', ['place_id' => $place->getId()]); + return $this->redirectToRoute('app_public_index'); } // Vérifier si le lieu a déjà été contacté @@ -971,10 +976,10 @@ final class AdminController extends AbstractController $this->entityManager->flush(); $place->setLastContactAttemptDate(new \DateTime()); - + $this->addFlash('success', 'Email envoyé avec succès à ' . $place->getName() . ' le ' . $place->getLastContactAttemptDate()->format('d/m/Y H:i:s')); return $this->redirectToRoute('app_public_index'); - } + } #[Route('/admin/fraicheur/histogramme', name: 'admin_fraicheur_histogramme')] public function showFraicheurHistogramme(): Response @@ -991,7 +996,7 @@ final class AdminController extends AbstractController public function calculateFraicheur(): Response { // Ajout d'un log d'action avec le service ActionLogger - $this->actionLogger->log('fraicheur/calculate' , []); + $this->actionLogger->log('fraicheur/calculate', []); $filesystem = new Filesystem(); $jsonPath = $this->getParameter('kernel.project_dir') . '/var/fraicheur_osm.json'; $now = new \DateTime(); @@ -1022,7 +1027,7 @@ final class AdminController extends AbstractController 'total' => $total, 'histogram' => $histogram ]; - $filesystem->dumpFile($jsonPath, json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); + $filesystem->dumpFile($jsonPath, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); // --- Distribution villes selon lieux/habitants --- $distJsonPath = $this->getParameter('kernel.project_dir') . '/var/distribution_villes_lieux_par_habitant.json'; @@ -1057,7 +1062,7 @@ final class AdminController extends AbstractController 'histogram_001' => $histogram_lieux_par_habitant, 'histogram_10' => $histogram_habitants_par_lieu ]; - $filesystem->dumpFile($distJsonPath, json_encode($distData, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); + $filesystem->dumpFile($distJsonPath, json_encode($distData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); return $this->redirectToRoute('admin_fraicheur_histogramme'); } @@ -1104,7 +1109,7 @@ final class AdminController extends AbstractController 'total_villes' => $totalVilles, 'histogram_001' => $histogram ]; - $filesystem->dumpFile($jsonPath, json_encode($distData, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); + $filesystem->dumpFile($jsonPath, json_encode($distData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); } $content = file_get_contents($jsonPath); $data = json_decode($content, true); @@ -1153,67 +1158,67 @@ final class AdminController extends AbstractController if ($budgetsMisAJour > 0) { $this->entityManager->flush(); } - $this->addFlash('success', $budgetsMisAJour.' budgets mis à jour.'); + $this->addFlash('success', $budgetsMisAJour . ' budgets mis à jour.'); return $this->redirectToRoute('app_admin'); } -#[Route('/admin/podium-contributeurs-osm', name: 'app_admin_podium_contributeurs_osm')] -public function podiumContributeursOsm(): Response -{ - // Ajout d'un log d'action avec le service ActionLogger - $this->actionLogger->log('podium_contributeurs_osm', []); - // On suppose que le champ "osmUser" existe sur l'entité Place - $placeRepo = $this->entityManager->getRepository(\App\Entity\Place::class); + #[Route('/admin/podium-contributeurs-osm', name: 'app_admin_podium_contributeurs_osm')] + public function podiumContributeursOsm(): Response + { + // Ajout d'un log d'action avec le service ActionLogger + $this->actionLogger->log('podium_contributeurs_osm', []); + // On suppose que le champ "osmUser" existe sur l'entité Place + $placeRepo = $this->entityManager->getRepository(\App\Entity\Place::class); - // Nouvelle requête groupée pour tout calculer d'un coup - $qb = $placeRepo->createQueryBuilder('p') - ->select( - 'p.osm_user', - 'COUNT(p.id) as nb', - 'AVG((CASE WHEN p.has_opening_hours = true THEN 1 ELSE 0 END) +' - . ' (CASE WHEN p.has_address = true THEN 1 ELSE 0 END) +' - . ' (CASE WHEN p.has_website = true THEN 1 ELSE 0 END) +' - . ' (CASE WHEN p.has_wheelchair = true THEN 1 ELSE 0 END) +' - . ' (CASE WHEN p.has_note = true THEN 1 ELSE 0 END)) / 5 * 100 as completion_moyen' - ) - ->where('p.osm_user IS NOT NULL') - ->andWhere("p.osm_user != ''") - ->groupBy('p.osm_user') - ->orderBy('nb', 'DESC') - ->setMaxResults(100); + // Nouvelle requête groupée pour tout calculer d'un coup + $qb = $placeRepo->createQueryBuilder('p') + ->select( + 'p.osm_user', + 'COUNT(p.id) as nb', + 'AVG((CASE WHEN p.has_opening_hours = true THEN 1 ELSE 0 END) +' + . ' (CASE WHEN p.has_address = true THEN 1 ELSE 0 END) +' + . ' (CASE WHEN p.has_website = true THEN 1 ELSE 0 END) +' + . ' (CASE WHEN p.has_wheelchair = true THEN 1 ELSE 0 END) +' + . ' (CASE WHEN p.has_note = true THEN 1 ELSE 0 END)) / 5 * 100 as completion_moyen' + ) + ->where('p.osm_user IS NOT NULL') + ->andWhere("p.osm_user != ''") + ->groupBy('p.osm_user') + ->orderBy('nb', 'DESC') + ->setMaxResults(100); - $podium = $qb->getQuery()->getResult(); + $podium = $qb->getQuery()->getResult(); - // Calcul du score pondéré et normalisation - $maxPondere = 0; - foreach ($podium as &$row) { - $row['completion_moyen'] = $row['completion_moyen'] !== null ? round($row['completion_moyen'], 1) : null; - $row['completion_pondere'] = ($row['completion_moyen'] !== null && $row['nb'] > 0) - ? round($row['completion_moyen'] * $row['nb'], 1) - : null; - if ($row['completion_pondere'] !== null && $row['completion_pondere'] > $maxPondere) { - $maxPondere = $row['completion_pondere']; - } - } - unset($row); - // Normalisation des scores pondérés entre 0 et 100 - if ($maxPondere > 0) { + // Calcul du score pondéré et normalisation + $maxPondere = 0; foreach ($podium as &$row) { - if ($row['completion_pondere'] !== null) { - $row['completion_pondere_normalisee'] = round($row['completion_pondere'] / $maxPondere * 100, 1); - } else { - $row['completion_pondere_normalisee'] = null; + $row['completion_moyen'] = $row['completion_moyen'] !== null ? round($row['completion_moyen'], 1) : null; + $row['completion_pondere'] = ($row['completion_moyen'] !== null && $row['nb'] > 0) + ? round($row['completion_moyen'] * $row['nb'], 1) + : null; + if ($row['completion_pondere'] !== null && $row['completion_pondere'] > $maxPondere) { + $maxPondere = $row['completion_pondere']; } } unset($row); - } - // Tri décroissant sur le score normalisé - usort($podium, function($a, $b) { - return ($b['completion_pondere_normalisee'] ?? 0) <=> ($a['completion_pondere_normalisee'] ?? 0); - }); + // Normalisation des scores pondérés entre 0 et 100 + if ($maxPondere > 0) { + foreach ($podium as &$row) { + if ($row['completion_pondere'] !== null) { + $row['completion_pondere_normalisee'] = round($row['completion_pondere'] / $maxPondere * 100, 1); + } else { + $row['completion_pondere_normalisee'] = null; + } + } + unset($row); + } + // Tri décroissant sur le score normalisé + usort($podium, function ($a, $b) { + return ($b['completion_pondere_normalisee'] ?? 0) <=> ($a['completion_pondere_normalisee'] ?? 0); + }); - return $this->render('admin/podium_contributeurs_osm.html.twig', [ - 'podium' => $podium - ]); -} + return $this->render('admin/podium_contributeurs_osm.html.twig', [ + 'podium' => $podium + ]); + } }