2025-05-26 11:55:44 +02:00
|
|
|
<?php
|
|
|
|
|
2025-06-05 15:09:28 +02:00
|
|
|
|
2025-05-26 11:55:44 +02:00
|
|
|
namespace App\Controller;
|
|
|
|
|
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
use Symfony\Component\Routing\Attribute\Route;
|
2025-05-26 12:57:10 +02:00
|
|
|
use App\Entity\Place;
|
2025-05-26 23:51:46 +02:00
|
|
|
use App\Entity\Stats;
|
2025-06-17 19:38:44 +02:00
|
|
|
use App\Entity\StatsHistory;
|
2025-05-26 12:57:10 +02:00
|
|
|
use App\Service\Motocultrice;
|
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
2025-06-21 11:28:31 +02:00
|
|
|
use Symfony\Component\HttpFoundation\Request;
|
2025-05-26 12:57:10 +02:00
|
|
|
use function uuid_create;
|
2025-05-26 11:55:44 +02:00
|
|
|
|
|
|
|
final class AdminController extends AbstractController
|
|
|
|
{
|
2025-05-26 12:57:10 +02:00
|
|
|
|
|
|
|
public function __construct(
|
|
|
|
private EntityManagerInterface $entityManager,
|
|
|
|
private Motocultrice $motocultrice
|
|
|
|
) {
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-06-19 01:04:44 +02:00
|
|
|
#[Route('/admin/labourer-toutes-les-zones', name: 'app_admin_labourer_tout')]
|
|
|
|
public function labourer_tout(): Response
|
|
|
|
{
|
|
|
|
|
|
|
|
$updateExisting =true;
|
|
|
|
|
|
|
|
$stats_all = $this->entityManager->getRepository(Stats::class)->findAll();
|
|
|
|
|
|
|
|
echo 'on a trouvé ' . count($stats_all) . ' zones à labourer<br>';
|
|
|
|
|
|
|
|
foreach($stats_all as $stats) {
|
|
|
|
|
|
|
|
echo '<br> 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<br>';
|
|
|
|
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 = $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)
|
2025-06-19 10:20:40 +02:00
|
|
|
->setNote($this->motocultrice->find_tag($placeData['tags'], 'note') ? true : false)
|
|
|
|
->setNoteContent($this->motocultrice->find_tag($placeData['tags'], 'note') ?? '')
|
|
|
|
->setPlaceCount(0)
|
|
|
|
// ->setOsmData($placeData['modified'] ?? null)
|
|
|
|
;
|
2025-06-19 01:04:44 +02:00
|
|
|
|
|
|
|
// Mettre à jour les données depuis Overpass
|
|
|
|
$place->update_place_from_overpass_data($placeData);
|
|
|
|
|
|
|
|
$this->entityManager->persist($place);
|
|
|
|
$stats->addPlace($place);
|
|
|
|
$processedCount++;
|
|
|
|
} elseif ($updateExisting) {
|
|
|
|
// Mettre à jour les données depuis Overpass uniquement si updateExisting est true
|
|
|
|
$existingPlace->update_place_from_overpass_data($placeData);
|
2025-06-21 10:26:55 +02:00
|
|
|
$stats->addPlace($existingPlace);
|
2025-06-19 01:04:44 +02:00
|
|
|
$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) {
|
|
|
|
$stats = new Stats();
|
|
|
|
$stats->setZone($insee_code);
|
|
|
|
}
|
|
|
|
|
|
|
|
$urls = $stats->getAllCTCUrlsMap();
|
|
|
|
|
|
|
|
$statsHistory = $this->entityManager->getRepository(StatsHistory::class)
|
|
|
|
->createQueryBuilder('sh')
|
|
|
|
->where('sh.stats = :stats')
|
|
|
|
->setParameter('stats', $stats)
|
|
|
|
->orderBy('sh.id', 'DESC')
|
|
|
|
->setMaxResults(365)
|
|
|
|
->getQuery()
|
|
|
|
->getResult();
|
|
|
|
|
|
|
|
// Calculer les statistiques
|
|
|
|
$calculatedStats = $this->motocultrice->calculateStats($commerces);
|
|
|
|
|
|
|
|
// Mettre à jour les stats pour la zone donnée
|
|
|
|
$stats->setPlacesCount($calculatedStats['places_count']);
|
|
|
|
$stats->setAvecHoraires($calculatedStats['counters']['avec_horaires']);
|
|
|
|
$stats->setAvecAdresse($calculatedStats['counters']['avec_adresse']);
|
|
|
|
$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
|
|
|
|
foreach ($commerces as $commerce) {
|
|
|
|
$commerce->setStats($stats);
|
|
|
|
$this->entityManager->persist($commerce);
|
|
|
|
}
|
|
|
|
|
|
|
|
$stats->computeCompletionPercent();
|
2025-06-19 12:49:30 +02:00
|
|
|
|
|
|
|
// Calculer les statistiques de fraîcheur des données OSM
|
|
|
|
$timestamps = [];
|
|
|
|
foreach ($stats->getPlaces() as $place) {
|
|
|
|
if ($place->getOsmDataDate()) {
|
|
|
|
$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) {
|
|
|
|
$stats->setDateCreated(new \DateTime());
|
|
|
|
}
|
|
|
|
|
|
|
|
$stats->setDateModified(new \DateTime());
|
|
|
|
|
|
|
|
// Créer un historique des statistiques
|
|
|
|
$statsHistory = new StatsHistory();
|
|
|
|
$statsHistory->setDate(new \DateTime())
|
|
|
|
->setStats($stats);
|
|
|
|
|
|
|
|
// Compter les Places avec email et SIRET
|
|
|
|
$placesWithEmail = 0;
|
|
|
|
$placesWithSiret = 0;
|
|
|
|
foreach ($stats->getPlaces() as $place) {
|
|
|
|
if ($place->getEmail() && $place->getEmail() !== '') {
|
|
|
|
$placesWithEmail++;
|
|
|
|
}
|
|
|
|
if ($place->getSiret() && $place->getSiret() !== '') {
|
|
|
|
$placesWithSiret++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$statsHistory->setPlacesCount($stats->getPlaces()->count())
|
|
|
|
->setOpeningHoursCount($stats->getAvecHoraires())
|
|
|
|
->setAddressCount($stats->getAvecAdresse())
|
|
|
|
->setWebsiteCount($stats->getAvecSite())
|
|
|
|
->setSiretCount($placesWithSiret)
|
|
|
|
->setEmailsCount($placesWithEmail)
|
|
|
|
// ->setAccessibiliteCount($stats->getAvecAccessibilite())
|
|
|
|
// ->setNoteCount($stats->getAvecNote())
|
|
|
|
->setCompletionPercent($stats->getCompletionPercent())
|
|
|
|
->setStats($stats);
|
|
|
|
|
|
|
|
$this->entityManager->persist($statsHistory);
|
|
|
|
|
|
|
|
|
2025-06-19 01:04:44 +02:00
|
|
|
$this->entityManager->persist($stats);
|
2025-06-19 12:49:30 +02:00
|
|
|
$this->entityManager->flush();
|
2025-06-19 01:04:44 +02:00
|
|
|
|
2025-06-19 12:49:30 +02:00
|
|
|
$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().').';
|
|
|
|
}
|
|
|
|
$this->addFlash('success', $message);
|
2025-06-19 01:04:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$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');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2025-05-26 11:55:44 +02:00
|
|
|
#[Route('/admin', name: 'app_admin')]
|
|
|
|
public function index(): Response
|
|
|
|
{
|
|
|
|
return $this->render('admin/index.html.twig', [
|
|
|
|
'controller_name' => 'AdminController',
|
|
|
|
]);
|
|
|
|
}
|
2025-05-26 12:57:10 +02:00
|
|
|
|
2025-06-17 18:27:19 +02:00
|
|
|
#[Route('/admin/stats/{insee_code}', name: 'app_admin_stats')]
|
|
|
|
public function calculer_stats(string $insee_code): Response
|
2025-05-26 23:51:46 +02:00
|
|
|
{
|
|
|
|
// Récupérer tous les commerces de la zone
|
2025-06-21 12:36:09 +02:00
|
|
|
// $commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code, 'dead' => false]);
|
2025-05-26 23:51:46 +02:00
|
|
|
|
|
|
|
// Récupérer les stats existantes pour la zone
|
2025-06-17 18:27:19 +02:00
|
|
|
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
2025-06-21 12:36:09 +02:00
|
|
|
$commerces = $stats->getPlaces();
|
|
|
|
|
2025-06-19 01:04:44 +02:00
|
|
|
if(!$stats) {
|
2025-06-21 11:28:31 +02:00
|
|
|
// Si aucune stat n'existe, on en crée une vide pour éviter les erreurs, mais sans la sauvegarder
|
2025-06-19 01:04:44 +02:00
|
|
|
$stats = new Stats();
|
|
|
|
$stats->setZone($insee_code);
|
2025-06-21 11:28:31 +02:00
|
|
|
$stats->setName('Nouvelle zone non labourée');
|
2025-06-19 01:04:44 +02:00
|
|
|
}
|
|
|
|
|
2025-06-21 11:28:31 +02:00
|
|
|
$urls = $stats->getAllCTCUrlsMap();
|
|
|
|
$statsHistory = $this->entityManager->getRepository(StatsHistory::class)
|
|
|
|
->createQueryBuilder('sh')
|
|
|
|
->where('sh.stats = :stats')
|
|
|
|
->setParameter('stats', $stats)
|
|
|
|
->orderBy('sh.id', 'DESC')
|
|
|
|
->setMaxResults(365)
|
|
|
|
->getQuery()
|
2025-06-21 12:36:09 +02:00
|
|
|
->getResult();
|
2025-06-19 12:49:30 +02:00
|
|
|
|
2025-06-21 11:28:31 +02:00
|
|
|
// Données pour le graphique des modifications par trimestre
|
|
|
|
$modificationsByQuarter = [];
|
2025-06-21 12:36:09 +02:00
|
|
|
foreach ($commerces as $commerce) {
|
2025-06-21 11:28:31 +02:00
|
|
|
if ($commerce->getOsmDataDate()) {
|
|
|
|
$date = $commerce->getOsmDataDate();
|
|
|
|
$year = $date->format('Y');
|
|
|
|
$quarter = ceil($date->format('n') / 3);
|
|
|
|
$key = $year . '-Q' . $quarter;
|
|
|
|
if (!isset($modificationsByQuarter[$key])) {
|
|
|
|
$modificationsByQuarter[$key] = 0;
|
|
|
|
}
|
|
|
|
$modificationsByQuarter[$key]++;
|
2025-06-19 12:49:30 +02:00
|
|
|
}
|
|
|
|
}
|
2025-06-21 11:28:31 +02:00
|
|
|
ksort($modificationsByQuarter); // Trier par clé (année-trimestre)
|
2025-06-19 12:49:30 +02:00
|
|
|
|
2025-06-21 12:36:09 +02:00
|
|
|
$geojson = [
|
|
|
|
'type' => 'FeatureCollection',
|
|
|
|
'features' => []
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ($commerces as $commerce) {
|
|
|
|
if ($commerce->getLat() && $commerce->getLon()) {
|
|
|
|
$geojson['features'][] = [
|
|
|
|
'type' => 'Feature',
|
|
|
|
'geometry' => [
|
|
|
|
'type' => 'Point',
|
|
|
|
'coordinates' => [$commerce->getLon(), $commerce->getLat()]
|
|
|
|
],
|
|
|
|
'properties' => [
|
|
|
|
'id' => $commerce->getOsmId(),
|
|
|
|
'name' => $commerce->getName(),
|
|
|
|
'main_tag' => $commerce->getMainTag(),
|
|
|
|
'address' => $commerce->getStreet() . ' ' . $commerce->getHousenumber(),
|
|
|
|
'note' => $commerce->getNoteContent(),
|
|
|
|
'osm_url' => 'https://www.openstreetmap.org/' . $commerce->getOsmKind() . '/' . $commerce->getOsmId()
|
|
|
|
]
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
2025-05-27 12:17:46 +02:00
|
|
|
|
2025-05-26 23:51:46 +02:00
|
|
|
return $this->render('admin/stats.html.twig', [
|
|
|
|
'stats' => $stats,
|
2025-06-21 11:28:31 +02:00
|
|
|
'commerces' => $commerces,
|
|
|
|
'urls' => $urls,
|
2025-06-21 12:36:09 +02:00
|
|
|
'geojson' => json_encode($geojson),
|
2025-06-21 11:28:31 +02:00
|
|
|
'modificationsByQuarter' => json_encode($modificationsByQuarter),
|
2025-06-01 18:56:01 +02:00
|
|
|
'maptiler_token' => $_ENV['MAPTILER_TOKEN'],
|
2025-06-17 18:27:19 +02:00
|
|
|
'statsHistory' => $statsHistory,
|
2025-06-18 00:41:24 +02:00
|
|
|
'CTC_urls' => $urls,
|
2025-05-26 23:51:46 +02:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2025-06-01 23:35:15 +02:00
|
|
|
#[Route('/admin/placeType/{osm_kind}/{osm_id}', name: 'app_admin_by_osm_id')]
|
|
|
|
public function placeType(string $osm_kind, string $osm_id): Response
|
|
|
|
{
|
|
|
|
$place = $this->entityManager->getRepository(Place::class)->findOneBy(['osm_kind' => $osm_kind, 'osmId' => $osm_id]);
|
|
|
|
if($place) {
|
|
|
|
return $this->redirectToRoute('app_admin_commerce', ['id' => $place->getId()]);
|
|
|
|
} else {
|
|
|
|
$this->addFlash('error', 'Le lieu n\'existe pas.');
|
|
|
|
return $this->redirectToRoute('app_public_index');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-06-04 00:16:56 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* rediriger vers l'url unique quand on est admin
|
|
|
|
*/
|
2025-05-28 16:24:34 +02:00
|
|
|
#[Route('/admin/commerce/{id}', name: 'app_admin_commerce')]
|
|
|
|
public function commerce(int $id): Response
|
|
|
|
{
|
|
|
|
|
|
|
|
// 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.');
|
|
|
|
return $this->redirectToRoute('app_public_index');
|
|
|
|
}
|
|
|
|
$commerce = $this->entityManager->getRepository(Place::class)->find($id);
|
|
|
|
|
|
|
|
if (!$commerce) {
|
|
|
|
throw $this->createNotFoundException('Commerce non trouvé');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Redirection vers la page de modification avec les paramètres nécessaires
|
|
|
|
return $this->redirectToRoute('app_public_edit', [
|
|
|
|
'zipcode' => $commerce->getZipCode(),
|
2025-05-29 13:24:50 +02:00
|
|
|
'name' => $commerce->getName()!='' ? $commerce->getName() : '?',
|
2025-05-28 16:24:34 +02:00
|
|
|
'uuid' => $commerce->getUuidForUrl()
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2025-06-04 00:16:56 +02:00
|
|
|
|
|
|
|
/**
|
2025-06-17 18:27:19 +02:00
|
|
|
* récupérer les commerces de la zone selon le code INSEE, créer les nouveaux lieux, et mettre à jour les existants
|
2025-06-04 00:16:56 +02:00
|
|
|
*/
|
2025-06-17 18:27:19 +02:00
|
|
|
#[Route('/admin/labourer/{insee_code}', name: 'app_admin_labourer')]
|
2025-06-21 11:28:31 +02:00
|
|
|
public function labourer(Request $request, string $insee_code, bool $updateExisting = true): Response
|
2025-05-26 12:57:10 +02:00
|
|
|
{
|
2025-06-21 11:28:31 +02:00
|
|
|
$deleteMissing = $request->query->getBoolean('deleteMissing', true);
|
2025-06-19 10:37:29 +02:00
|
|
|
|
|
|
|
// Vérifier si le code INSEE est valide (composé uniquement de chiffres)
|
|
|
|
if (!ctype_digit($insee_code) || $insee_code == 'undefined' || $insee_code == '') {
|
|
|
|
$this->addFlash('error', 'Code INSEE invalide : il doit être composé uniquement de chiffres.');
|
|
|
|
return $this->redirectToRoute('app_public_index');
|
|
|
|
}
|
2025-06-05 16:20:20 +02:00
|
|
|
try {
|
|
|
|
// Récupérer ou créer les stats pour cette zone
|
2025-06-17 18:27:19 +02:00
|
|
|
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
2025-06-05 17:32:12 +02:00
|
|
|
|
2025-06-17 18:27:19 +02:00
|
|
|
$city = $this->motocultrice->get_city_osm_from_zip_code($insee_code);
|
2025-06-05 16:20:20 +02:00
|
|
|
if (!$stats) {
|
|
|
|
$stats = new Stats();
|
2025-06-17 19:38:44 +02:00
|
|
|
$stats->setDateCreated(new \DateTime());
|
|
|
|
$stats->setDateModified(new \DateTime());
|
2025-06-17 18:27:19 +02:00
|
|
|
$stats->setZone($insee_code)
|
2025-06-05 16:20:20 +02:00
|
|
|
->setPlacesCount(0)
|
|
|
|
->setAvecHoraires(0)
|
|
|
|
->setAvecAdresse(0)
|
|
|
|
->setAvecSite(0)
|
|
|
|
->setAvecAccessibilite(0)
|
|
|
|
->setAvecNote(0)
|
|
|
|
->setCompletionPercent(0);
|
2025-06-17 16:23:29 +02:00
|
|
|
$this->entityManager->persist($stats);
|
|
|
|
$this->entityManager->flush();
|
|
|
|
}
|
2025-06-05 17:32:12 +02:00
|
|
|
$stats->setName($city);
|
2025-06-04 00:16:56 +02:00
|
|
|
|
2025-06-17 16:23:29 +02:00
|
|
|
// Récupérer la population via l'API
|
|
|
|
$population = null;
|
|
|
|
try {
|
2025-06-17 18:27:19 +02:00
|
|
|
$apiUrl = 'https://geo.api.gouv.fr/communes/' . $insee_code;
|
2025-06-17 16:23:29 +02:00
|
|
|
$response = file_get_contents($apiUrl);
|
|
|
|
if ($response !== false) {
|
|
|
|
$data = json_decode($response, true);
|
|
|
|
if (isset($data['population'])) {
|
|
|
|
$population = (int)$data['population'];
|
|
|
|
$stats->setPopulation($population);
|
|
|
|
}
|
2025-06-17 18:27:19 +02:00
|
|
|
if (isset($data['siren'])) {
|
|
|
|
$stats->setSiren((int)$data['siren']);
|
|
|
|
}
|
|
|
|
if (isset($data['codeEpci'])) {
|
|
|
|
$stats->setCodeEpci((int)$data['codeEpci']);
|
|
|
|
}
|
|
|
|
if (isset($data['codesPostaux'])) {
|
|
|
|
$stats->setCodesPostaux(implode(';', $data['codesPostaux']));
|
|
|
|
}
|
2025-06-17 16:23:29 +02:00
|
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
2025-06-17 18:27:19 +02:00
|
|
|
$this->addFlash('error', 'Erreur lors de la récupération des données de l\'API : ' . $e->getMessage());
|
|
|
|
|
2025-06-17 16:23:29 +02:00
|
|
|
}
|
|
|
|
|
2025-06-05 16:20:20 +02:00
|
|
|
// Récupérer toutes les données
|
2025-06-21 10:26:55 +02:00
|
|
|
$places_overpass = $this->motocultrice->labourer($insee_code);
|
2025-06-05 16:20:20 +02:00
|
|
|
$processedCount = 0;
|
|
|
|
$updatedCount = 0;
|
2025-06-21 10:26:55 +02:00
|
|
|
$deletedCount = 0;
|
|
|
|
|
|
|
|
$overpass_osm_ids = array_map(fn($place) => $place['id'], $places_overpass);
|
|
|
|
|
|
|
|
foreach ($places_overpass as $placeData) {
|
2025-06-05 16:20:20 +02:00
|
|
|
// 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'])
|
2025-06-17 18:27:19 +02:00
|
|
|
->setZipCode($insee_code)
|
2025-06-05 16:20:20 +02:00
|
|
|
->setUuidForUrl($this->motocultrice->uuid_create())
|
|
|
|
->setModifiedDate(new \DateTime())
|
|
|
|
->setStats($stats)
|
|
|
|
->setDead(false)
|
|
|
|
->setOptedOut(false)
|
2025-06-09 00:11:16 +02:00
|
|
|
->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']) ?? '')
|
2025-06-05 16:20:20 +02:00
|
|
|
->setAskedHumainsSupport(false)
|
|
|
|
->setLastContactAttemptDate(null)
|
2025-06-21 10:26:55 +02:00
|
|
|
->setNote($this->motocultrice->find_tag($placeData['tags'], 'note') ? true : false)
|
|
|
|
->setNoteContent($this->motocultrice->find_tag($placeData['tags'], 'note') ?? '')
|
|
|
|
->setPlaceCount(0)
|
|
|
|
// ->setOsmData($placeData['modified'] ?? null)
|
|
|
|
;
|
2025-06-05 16:20:20 +02:00
|
|
|
|
|
|
|
// Mettre à jour les données depuis Overpass
|
|
|
|
$place->update_place_from_overpass_data($placeData);
|
2025-06-04 00:16:56 +02:00
|
|
|
|
2025-06-05 16:20:20 +02:00
|
|
|
$this->entityManager->persist($place);
|
|
|
|
$stats->addPlace($place);
|
|
|
|
$processedCount++;
|
|
|
|
} elseif ($updateExisting) {
|
2025-06-21 10:26:55 +02:00
|
|
|
// Mettre à jour les données depuis Overpass et s'assurer qu'il est marqué comme "vivant"
|
|
|
|
$existingPlace->setDead(false);
|
2025-06-05 16:20:20 +02:00
|
|
|
$existingPlace->update_place_from_overpass_data($placeData);
|
2025-06-21 10:26:55 +02:00
|
|
|
$stats->addPlace($existingPlace);
|
2025-06-05 16:20:20 +02:00
|
|
|
$this->entityManager->persist($existingPlace);
|
|
|
|
$updatedCount++;
|
2025-06-03 16:19:07 +02:00
|
|
|
}
|
|
|
|
}
|
2025-06-05 16:20:20 +02:00
|
|
|
|
2025-06-21 11:28:31 +02:00
|
|
|
// Supprimer les lieux qui ne sont plus dans la réponse Overpass, si activé
|
|
|
|
if ($deleteMissing) {
|
|
|
|
$db_places = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
|
|
|
|
foreach ($db_places as $db_place) {
|
|
|
|
if (!in_array($db_place->getOsmId(), $overpass_osm_ids)) {
|
|
|
|
$this->entityManager->remove($db_place);
|
|
|
|
$deletedCount++;
|
|
|
|
}
|
2025-06-21 10:26:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
$stats = new Stats();
|
|
|
|
$stats->setZone($insee_code);
|
|
|
|
}
|
|
|
|
|
|
|
|
$urls = $stats->getAllCTCUrlsMap();
|
2025-06-05 16:20:20 +02:00
|
|
|
|
2025-06-21 10:26:55 +02:00
|
|
|
$statsHistory = $this->entityManager->getRepository(StatsHistory::class)
|
|
|
|
->createQueryBuilder('sh')
|
|
|
|
->where('sh.stats = :stats')
|
|
|
|
->setParameter('stats', $stats)
|
|
|
|
->orderBy('sh.id', 'DESC')
|
|
|
|
->setMaxResults(365)
|
|
|
|
->getQuery()
|
|
|
|
->getResult();
|
|
|
|
|
|
|
|
// Calculer les statistiques
|
|
|
|
$calculatedStats = $this->motocultrice->calculateStats($commerces);
|
|
|
|
|
|
|
|
// Mettre à jour les stats pour la zone donnée
|
|
|
|
$stats->setPlacesCount($calculatedStats['places_count']);
|
|
|
|
$stats->setAvecHoraires($calculatedStats['counters']['avec_horaires']);
|
|
|
|
$stats->setAvecAdresse($calculatedStats['counters']['avec_adresse']);
|
|
|
|
$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
|
|
|
|
foreach ($commerces as $commerce) {
|
|
|
|
$commerce->setStats($stats);
|
|
|
|
$this->entityManager->persist($commerce);
|
|
|
|
}
|
|
|
|
|
2025-06-05 16:20:20 +02:00
|
|
|
$stats->computeCompletionPercent();
|
2025-06-05 17:32:12 +02:00
|
|
|
|
2025-06-19 12:49:30 +02:00
|
|
|
// Calculer les statistiques de fraîcheur des données OSM
|
|
|
|
$timestamps = [];
|
|
|
|
foreach ($stats->getPlaces() as $place) {
|
|
|
|
if ($place->getOsmDataDate()) {
|
|
|
|
$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));
|
|
|
|
}
|
|
|
|
|
2025-06-17 19:38:44 +02:00
|
|
|
if($stats->getDateCreated() == null) {
|
|
|
|
$stats->setDateCreated(new \DateTime());
|
|
|
|
}
|
|
|
|
|
|
|
|
$stats->setDateModified(new \DateTime());
|
|
|
|
|
2025-06-17 18:27:19 +02:00
|
|
|
// Créer un historique des statistiques
|
|
|
|
$statsHistory = new StatsHistory();
|
2025-06-17 19:38:44 +02:00
|
|
|
$statsHistory->setDate(new \DateTime())
|
|
|
|
->setStats($stats);
|
|
|
|
|
2025-06-19 11:07:54 +02:00
|
|
|
// Compter les Places avec email et SIRET
|
|
|
|
$placesWithEmail = 0;
|
|
|
|
$placesWithSiret = 0;
|
|
|
|
foreach ($stats->getPlaces() as $place) {
|
|
|
|
if ($place->getEmail() && $place->getEmail() !== '') {
|
|
|
|
$placesWithEmail++;
|
|
|
|
}
|
|
|
|
if ($place->getSiret() && $place->getSiret() !== '') {
|
|
|
|
$placesWithSiret++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-17 18:27:19 +02:00
|
|
|
$statsHistory->setPlacesCount($stats->getPlaces()->count())
|
2025-06-17 19:38:44 +02:00
|
|
|
->setOpeningHoursCount($stats->getAvecHoraires())
|
|
|
|
->setAddressCount($stats->getAvecAdresse())
|
|
|
|
->setWebsiteCount($stats->getAvecSite())
|
2025-06-19 11:07:54 +02:00
|
|
|
->setSiretCount($placesWithSiret)
|
|
|
|
->setEmailsCount($placesWithEmail)
|
2025-06-17 19:38:44 +02:00
|
|
|
// ->setAccessibiliteCount($stats->getAvecAccessibilite())
|
|
|
|
// ->setNoteCount($stats->getAvecNote())
|
2025-06-17 18:27:19 +02:00
|
|
|
->setCompletionPercent($stats->getCompletionPercent())
|
|
|
|
->setStats($stats);
|
|
|
|
|
|
|
|
$this->entityManager->persist($statsHistory);
|
|
|
|
|
2025-06-17 19:38:44 +02:00
|
|
|
|
2025-06-05 16:20:20 +02:00
|
|
|
$this->entityManager->persist($stats);
|
|
|
|
$this->entityManager->flush();
|
|
|
|
|
|
|
|
$message = 'Labourage terminé avec succès. ' . $processedCount . ' nouveaux lieux traités.';
|
|
|
|
if ($updateExisting) {
|
2025-06-21 10:26:55 +02:00
|
|
|
$message .= ' ' . $updatedCount . ' lieux existants mis à jour.';
|
|
|
|
}
|
|
|
|
if ($deletedCount > 0) {
|
|
|
|
$message .= ' ' . $deletedCount . ' lieux ont été supprimés.';
|
2025-06-05 16:20:20 +02:00
|
|
|
}
|
2025-06-21 10:26:55 +02:00
|
|
|
$message .= ' Zone : '.$stats->getName().' ('.$stats->getZone().').';
|
2025-06-05 16:20:20 +02:00
|
|
|
$this->addFlash('success', $message);
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
$this->addFlash('error', 'Erreur lors du labourage : ' . $e->getMessage());
|
2025-06-17 19:38:44 +02:00
|
|
|
die(var_dump($e));
|
2025-05-26 12:57:10 +02:00
|
|
|
}
|
2025-06-04 00:16:56 +02:00
|
|
|
|
2025-06-17 19:38:44 +02:00
|
|
|
// return $this->redirectToRoute('app_public_dashboard');
|
|
|
|
return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]);
|
2025-05-26 12:57:10 +02:00
|
|
|
}
|
2025-05-26 23:51:46 +02:00
|
|
|
|
|
|
|
#[Route('/admin/delete/{id}', name: 'app_admin_delete')]
|
|
|
|
public function delete(int $id): Response
|
|
|
|
{
|
|
|
|
$commerce = $this->entityManager->getRepository(Place::class)->find($id);
|
2025-05-28 16:24:34 +02:00
|
|
|
if($commerce) {
|
|
|
|
$this->entityManager->remove($commerce);
|
|
|
|
$this->entityManager->flush();
|
2025-05-26 23:51:46 +02:00
|
|
|
|
2025-05-28 16:24:34 +02:00
|
|
|
$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.');
|
|
|
|
}
|
2025-05-26 23:51:46 +02:00
|
|
|
|
2025-05-28 16:24:34 +02:00
|
|
|
return $this->redirectToRoute('app_public_dashboard');
|
2025-05-26 23:51:46 +02:00
|
|
|
}
|
|
|
|
|
2025-06-17 18:27:19 +02:00
|
|
|
#[Route('/admin/delete_by_zone/{insee_code}', name: 'app_admin_delete_by_zone')]
|
|
|
|
public function delete_by_zone(string $insee_code): Response
|
2025-05-26 23:51:46 +02:00
|
|
|
{
|
2025-06-17 18:27:19 +02:00
|
|
|
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
2025-06-21 11:28:31 +02:00
|
|
|
|
|
|
|
if (!$stats) {
|
|
|
|
$this->addFlash('error', 'Aucune statistique trouvée pour la zone ' . $insee_code);
|
|
|
|
return $this->redirectToRoute('app_public_dashboard');
|
2025-05-26 23:51:46 +02:00
|
|
|
}
|
|
|
|
|
2025-06-21 11:28:31 +02:00
|
|
|
try {
|
|
|
|
// 1. Supprimer tous les StatsHistory associés
|
|
|
|
foreach ($stats->getStatsHistories() as $history) {
|
|
|
|
$this->entityManager->remove($history);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. Supprimer tous les Places associées
|
|
|
|
foreach ($stats->getPlaces() as $place) {
|
|
|
|
$this->entityManager->remove($place);
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3. Supprimer l'objet Stats lui-même
|
|
|
|
$this->entityManager->remove($stats);
|
|
|
|
|
|
|
|
// 4. Appliquer les changements à la base de données
|
|
|
|
$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());
|
|
|
|
}
|
2025-05-26 23:51:46 +02:00
|
|
|
|
2025-05-28 16:24:34 +02:00
|
|
|
return $this->redirectToRoute('app_public_dashboard');
|
|
|
|
}
|
2025-05-26 23:51:46 +02:00
|
|
|
|
|
|
|
|
|
|
|
#[Route('/admin/export', name: 'app_admin_export')]
|
|
|
|
public function export(): Response
|
|
|
|
{
|
|
|
|
$places = $this->entityManager->getRepository(Place::class)->findAll();
|
|
|
|
|
|
|
|
$csvData = [];
|
|
|
|
$csvData[] = [
|
|
|
|
'Nom',
|
|
|
|
'Email',
|
|
|
|
'Code postal',
|
|
|
|
'ID OSM',
|
|
|
|
'Type OSM',
|
|
|
|
'Date de modification',
|
|
|
|
'Date dernier contact',
|
|
|
|
'Note',
|
|
|
|
'Désabonné',
|
|
|
|
'Inactif',
|
|
|
|
'Support humain demandé',
|
|
|
|
'A des horaires',
|
|
|
|
'A une adresse',
|
|
|
|
'A un site web',
|
|
|
|
'A accessibilité',
|
|
|
|
'A une note'
|
|
|
|
];
|
|
|
|
|
|
|
|
foreach ($places as $place) {
|
|
|
|
$csvData[] = [
|
|
|
|
$place->getName(),
|
|
|
|
$place->getEmail(),
|
|
|
|
$place->getZipCode(),
|
|
|
|
$place->getOsmId(),
|
|
|
|
$place->getOsmKind(),
|
|
|
|
$place->getModifiedDate() ? $place->getModifiedDate()->format('Y-m-d H:i:s') : '',
|
|
|
|
$place->getLastContactAttemptDate() ? $place->getLastContactAttemptDate()->format('Y-m-d H:i:s') : '',
|
|
|
|
$place->getNote(),
|
|
|
|
$place->isOptedOut() ? 'Oui' : 'Non',
|
|
|
|
$place->isDead() ? 'Oui' : 'Non',
|
|
|
|
$place->isAskedHumainsSupport() ? 'Oui' : 'Non',
|
|
|
|
$place->hasOpeningHours() ? 'Oui' : 'Non',
|
|
|
|
$place->hasAddress() ? 'Oui' : 'Non',
|
|
|
|
$place->hasWebsite() ? 'Oui' : 'Non',
|
|
|
|
$place->hasWheelchair() ? 'Oui' : 'Non',
|
|
|
|
$place->hasNote() ? 'Oui' : 'Non'
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
$response = new Response();
|
|
|
|
$response->headers->set('Content-Type', 'text/csv');
|
|
|
|
$response->headers->set('Content-Disposition', 'attachment; filename="export_places.csv"');
|
|
|
|
|
|
|
|
$handle = fopen('php://temp', 'r+');
|
|
|
|
foreach ($csvData as $row) {
|
|
|
|
fputcsv($handle, $row, ';');
|
|
|
|
}
|
|
|
|
rewind($handle);
|
|
|
|
$response->setContent(stream_get_contents($handle));
|
|
|
|
fclose($handle);
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
}
|
2025-06-17 18:27:19 +02:00
|
|
|
#[Route('/admin/export_csv/{insee_code}', name: 'app_admin_export_csv')]
|
|
|
|
public function export_csv(string $insee_code): Response
|
2025-06-03 13:04:09 +02:00
|
|
|
{
|
2025-06-17 18:27:19 +02:00
|
|
|
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
|
|
|
$response = new Response($this->motocultrice->export($insee_code));
|
2025-06-03 13:04:09 +02:00
|
|
|
$response->headers->set('Content-Type', 'text/csv');
|
|
|
|
|
|
|
|
$slug_name = str_replace(' ', '-', $stats->getName());
|
2025-06-03 16:19:07 +02:00
|
|
|
|
2025-06-17 18:27:19 +02:00
|
|
|
$response->headers->set('Content-Disposition', 'attachment; filename="osm-commerces-export_' . $insee_code . '_' . $slug_name . '_' . date('Y-m-d_H-i-s') . '.csv"');
|
2025-06-03 13:04:09 +02:00
|
|
|
|
|
|
|
return $response;
|
|
|
|
}
|
2025-06-19 10:20:40 +02:00
|
|
|
|
|
|
|
#[Route('/admin/make_email_for_place/{id}', name: 'app_admin_make_email_for_place')]
|
|
|
|
public function make_email_for_place(Place $place): Response
|
|
|
|
{
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
|
|
|
$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.');
|
|
|
|
|
|
|
|
return $this->redirectToRoute('app_public_index');
|
|
|
|
}
|
2025-06-19 10:37:29 +02:00
|
|
|
|
|
|
|
#[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
|
|
|
|
{
|
|
|
|
|
|
|
|
// 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.');
|
|
|
|
return $this->redirectToRoute('app_public_index');
|
|
|
|
}
|
|
|
|
// Vérifier si le lieu a déjà été contacté
|
|
|
|
if ($place->getLastContactAttemptDate() !== null) {
|
|
|
|
$this->addFlash('error', 'Ce lieu a déjà été contacté le ' . $place->getLastContactAttemptDate()->format('d/m/Y H:i:s'));
|
|
|
|
return $this->redirectToRoute('app_public_index');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Générer le contenu de l'email avec le template
|
|
|
|
$emailContent = $this->renderView('admin/email_content.html.twig', [
|
|
|
|
'place' => $place
|
|
|
|
]);
|
|
|
|
|
|
|
|
// Envoyer l'email
|
|
|
|
$email = (new \Symfony\Component\Mime\Email())
|
|
|
|
->from('contact@openstreetmap.fr')
|
|
|
|
->to('contact+send_email@cipherbliss.com')
|
|
|
|
->subject('Mise à jour des informations de votre établissement dans OpenStreetMap')
|
|
|
|
->html($emailContent);
|
|
|
|
|
|
|
|
$mailer->send($email);
|
|
|
|
|
|
|
|
|
|
|
|
// Mettre à jour la date de dernier contact
|
|
|
|
$place->setLastContactAttemptDate(new \DateTime());
|
|
|
|
$this->entityManager->persist($place);
|
|
|
|
$this->entityManager->flush();
|
|
|
|
|
|
|
|
$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');
|
|
|
|
}
|
2025-05-26 11:55:44 +02:00
|
|
|
}
|