add logging data for edit form

This commit is contained in:
Tykayn 2025-06-27 00:35:11 +02:00 committed by tykayn
parent f7d659119a
commit b3d4064841

View file

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