osm-labo/src/Controller/AdminController.php

598 lines
25 KiB
PHP
Raw Normal View History

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;
use App\Entity\Place;
use App\Entity\Stats;
2025-06-17 19:38:44 +02:00
use App\Entity\StatsHistory;
use App\Service\Motocultrice;
use Doctrine\ORM\EntityManagerInterface;
use function uuid_create;
2025-05-26 11:55:44 +02:00
final class AdminController extends AbstractController
{
public function __construct(
private EntityManagerInterface $entityManager,
private Motocultrice $motocultrice
) {
}
#[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)
->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++;
} elseif ($updateExisting) {
// Mettre à jour les données depuis Overpass uniquement si updateExisting est true
$existingPlace->update_place_from_overpass_data($placeData);
$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();
$this->entityManager->persist($stats);
}
$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-06-17 18:27:19 +02:00
#[Route('/admin/stats/{insee_code}', name: 'app_admin_stats')]
public function calculer_stats(string $insee_code): Response
{
// Récupérer tous les commerces de la zone
2025-06-17 18:27:19 +02:00
$commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
// 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]);
if(!$stats) {
$stats = new Stats();
$stats->setZone($insee_code);
}
2025-06-18 00:41:24 +02:00
$urls = $stats->getAllCTCUrlsMap();
2025-06-17 18:27:19 +02:00
// 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);
}
$this->entityManager->persist($stats);
$this->entityManager->flush();
2025-05-27 12:17:46 +02:00
$stats->computeCompletionPercent();
$this->entityManager->persist($stats);
$this->entityManager->flush();
// Récupérer l'historique des stats APRÈS que l'entité soit persistée
$statsHistory = $this->entityManager->getRepository(StatsHistory::class)
->createQueryBuilder('sh')
->where('sh.stats = :stats')
->setParameter('stats', $stats)
->orderBy('sh.id', 'DESC')
->setMaxResults(365)
->getQuery()
->getResult();
2025-05-27 12:17:46 +02:00
return $this->render('admin/stats.html.twig', [
'stats' => $stats,
2025-06-17 18:27:19 +02:00
'insee_code' => $insee_code,
'query_places' => $this->motocultrice->get_query_places($insee_code),
'counters' => $calculatedStats['counters'],
'maptiler_token' => $_ENV['MAPTILER_TOKEN'],
'mapbox_token' => $_ENV['MAPBOX_TOKEN'],
2025-06-17 18:27:19 +02:00
'statsHistory' => $statsHistory,
2025-06-18 00:41:24 +02:00
'CTC_urls' => $urls,
]);
}
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');
}
}
/**
* 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(),
'name' => $commerce->getName()!='' ? $commerce->getName() : '?',
2025-05-28 16:24:34 +02:00
'uuid' => $commerce->getUuidForUrl()
]);
}
/**
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-17 18:27:19 +02:00
#[Route('/admin/labourer/{insee_code}', name: 'app_admin_labourer')]
public function labourer(string $insee_code, bool $updateExisting = true): Response
{
// 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-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-17 18:27:19 +02:00
$places = $this->motocultrice->labourer($insee_code);
2025-06-05 16:20:20 +02:00
$processedCount = 0;
$updatedCount = 0;
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'])
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)
->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)
->setNote('')
->setPlaceCount(0);
// Mettre à jour les données depuis Overpass
$place->update_place_from_overpass_data($placeData);
2025-06-05 16:20:20 +02:00
$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);
$this->entityManager->persist($existingPlace);
$updatedCount++;
2025-06-03 16:19:07 +02:00
}
}
2025-06-05 16:20:20 +02:00
// Flush final
$this->entityManager->flush();
// Mettre à jour les statistiques finales
$stats->computeCompletionPercent();
2025-06-05 17:32:12 +02:00
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) {
$message .= ' ' . $updatedCount . ' lieux existants mis à jour pour la 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-06-17 19:38:44 +02:00
// return $this->redirectToRoute('app_public_dashboard');
return $this->redirectToRoute('app_admin_stats', ['insee_code' => $insee_code]);
}
#[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-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-28 16:24:34 +02:00
return $this->redirectToRoute('app_public_dashboard');
}
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-06-17 18:27:19 +02:00
$commerces = $this->entityManager->getRepository(Place::class)->findBy(['zip_code' => $insee_code]);
$stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
foreach ($commerces as $commerce) {
$this->entityManager->remove($commerce);
}
2025-06-01 19:52:56 +02:00
$this->entityManager->remove($stats);
$this->entityManager->flush();
2025-06-17 18:27:19 +02:00
$this->addFlash('success', 'Tous les commerces de la zone '.$insee_code.' ont été supprimés avec succès de OSM Mes commerces, mais pas dans OpenStreetMap.');
2025-05-28 16:24:34 +02:00
return $this->redirectToRoute('app_public_dashboard');
}
#[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;
}
#[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');
}
#[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
}