From 6134381422f76261bf6aeeab1f454996df947b3b Mon Sep 17 00:00:00 2001 From: Tykayn Date: Sat, 2 Aug 2025 10:59:49 +0200 Subject: [PATCH] up commande de labourage --- src/Command/ProcessLabourageQueueCommand.php | 10 + src/Controller/PublicController.php | 393 ++----------------- 2 files changed, 38 insertions(+), 365 deletions(-) diff --git a/src/Command/ProcessLabourageQueueCommand.php b/src/Command/ProcessLabourageQueueCommand.php index 3143b376..3b347d50 100644 --- a/src/Command/ProcessLabourageQueueCommand.php +++ b/src/Command/ProcessLabourageQueueCommand.php @@ -142,8 +142,18 @@ class ProcessLabourageQueueCommand extends Command } } $stats->setDateLabourageDone(new \DateTime()); + // update completion + $stats->computeCompletionPercent(); + $io->info('Pourcentage de complétion: ' . $stats->getCompletionPercent()); $this->entityManager->persist($stats); $this->entityManager->flush(); + + $io->info('Récupération des followups de cette ville...'); + $this->followUpService->generateCityFollowUps($stats, $this->motocultrice, $this->entityManager); + + $this->entityManager->persist($stats); + $this->entityManager->flush(); + $io->success("Labourage terminé : $processedCount nouveaux lieux, $updatedCount lieux mis à jour."); return Command::SUCCESS; } diff --git a/src/Controller/PublicController.php b/src/Controller/PublicController.php index 53d13e23..8978499b 100644 --- a/src/Controller/PublicController.php +++ b/src/Controller/PublicController.php @@ -42,7 +42,7 @@ class PublicController extends AbstractController $data = $this->motocultrice->get_osm_object_data($type, $id); // Récupérer le code postal depuis les tags, sinon mettre -1 $zipCode = isset($data['tags_converted']['addr:postcode']) ? (int)$data['tags_converted']['addr:postcode'] : -1; - $place_name = $data['tags_converted']['name']; + $place_name = $data['tags_converted']['name'] ?? 'Commerce'; // Vérifier si une Place existe déjà avec le même osm_kind et osmId $existingPlace = $this->entityManager->getRepository(Place::class)->findOneBy([ @@ -96,10 +96,10 @@ class PublicController extends AbstractController $debug = ''; if ($this->getParameter('kernel.environment') !== 'prod') { $debug = 'Bonjour, nous sommes des bénévoles d\'OpenStreetMap France et nous vous proposons de modifier les informations de votre commerce. Voici votre lien unique de modification: ' . $this->generateUrl('app_public_edit', [ - 'zipcode' => $zipCode, - 'name' => $place_name, - 'uuid' => $place->getUuidForUrl() - ], true); + 'zipcode' => $zipCode, + 'name' => $place_name, + 'uuid' => $place->getUuidForUrl() + ], true); } $this->addFlash('success', 'Un email vous sera envoyé avec le lien de modification. ' . $debug); } @@ -112,147 +112,16 @@ class PublicController extends AbstractController ->to($destinataire) ->subject('Votre lien de modification OpenStreetMap') ->text('Bonjour, nous sommes des bénévoles d\'OpenStreetMap France et nous vous proposons de modifier les informations de votre commerce. Voici votre lien unique de modification: ' . $this->generateUrl('app_public_edit', [ - 'zipcode' => $zipCode, - 'name' => $place_name, - 'uuid' => $existingPlace ? $existingPlace->getUuidForUrl() : $place->getUuidForUrl() - ], true)); + 'zipcode' => $zipCode, + 'name' => $place_name, + 'uuid' => $existingPlace ? $existingPlace->getUuidForUrl() : $place->getUuidForUrl() + ], true)); $this->mailer->send($message); return $this->redirectToRoute('app_public_index'); } - #[Route('/api/demande/create', name: 'app_public_create_demande', methods: ['POST'])] - public function createDemande(Request $request): JsonResponse - { - $data = json_decode($request->getContent(), true); - - if (!isset($data['businessName']) || empty($data['businessName'])) { - return new JsonResponse(['success' => false, 'message' => 'Le nom du commerce est requis'], 400); - } - - // Create a new Demande - $demande = new Demande(); - $demande->setQuery($data['businessName']); - $demande->setStatus('new'); - $demande->setCreatedAt(new \DateTime()); - - // Save the INSEE code if provided - if (isset($data['insee']) && !empty($data['insee'])) { - $demande->setInsee((int)$data['insee']); - } - - // Save the OSM object type if provided - if (isset($data['osmObjectType']) && !empty($data['osmObjectType'])) { - $demande->setOsmObjectType($data['osmObjectType']); - } - - // Save the OSM ID if provided - if (isset($data['osmId']) && !empty($data['osmId'])) { - $demande->setOsmId($data['osmId']); - } - - // Check if a Place exists with the same OSM ID and type - $place = null; - if ($demande->getOsmId() && $demande->getOsmObjectType()) { - $existingPlace = $this->entityManager->getRepository(Place::class)->findOneBy([ - 'osm_kind' => $demande->getOsmObjectType(), - 'osmId' => $demande->getOsmId() - ]); - - if ($existingPlace) { - // Link the Place UUID to the Demande - $demande->setPlaceUuid($existingPlace->getUuidForUrl()); - $demande->setPlace($existingPlace); - $place = $existingPlace; - } else { - // Create a new Place if one doesn't exist - $place = new Place(); - $place->setOsmId((string)$demande->getOsmId()); - $place->setOsmKind($demande->getOsmObjectType()); - - // Get OSM data from Overpass API - $commerce_overpass = $this->motocultrice->get_osm_object_data($demande->getOsmObjectType(), $demande->getOsmId()); - - if ($commerce_overpass) { - // Update the Place with OSM data - $place->update_place_from_overpass_data($commerce_overpass); - - // Link the Place to the Demande - $demande->setPlaceUuid($place->getUuidForUrl()); - $demande->setPlace($place); - - // Persist the Place - $this->entityManager->persist($place); - } - } - - // Link the Place to a Stat object using the INSEE code - if ($place && $demande->getInsee()) { - $stats = $place->getStats(); - if (!$stats) { - $stats_exist = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $demande->getInsee()]); - if ($stats_exist) { - $stats = $stats_exist; - } else { - $stats = new Stats(); - $zipcode = (string)$demande->getInsee(); - $stats->setZone($zipcode); - $place->setZipCode($zipcode); - $this->entityManager->persist($stats); - } - - - $stats->addPlace($place); - $place->setStats($stats); - } - } - } - - if (!$place->getUuidForUrl()) { - $place->setUuidForUrl(uniqid()); - } - if (!$place->getZipCode()) { - $place->setZipCode("00000"); - } - - $this->entityManager->persist($demande); - $this->entityManager->flush(); - - return new JsonResponse([ - 'success' => true, - 'message' => 'Demande créée avec succès', - 'id' => $demande->getId() - ]); - } - - #[Route('/api/demande/{id}/email', name: 'app_public_update_demande_email', methods: ['POST'])] - public function updateDemandeEmail(int $id, Request $request): JsonResponse - { - $data = json_decode($request->getContent(), true); - - if (!isset($data['email']) || empty($data['email'])) { - return new JsonResponse(['success' => false, 'message' => 'L\'email est requis'], 400); - } - - $demande = $this->entityManager->getRepository(Demande::class)->find($id); - - if (!$demande) { - return new JsonResponse(['success' => false, 'message' => 'Demande non trouvée'], 404); - } - - $demande->setEmail($data['email']); - $demande->setStatus('email_provided'); - - $this->entityManager->persist($demande); - $this->entityManager->flush(); - - return new JsonResponse([ - 'success' => true, - 'message' => 'Email ajouté avec succès' - ]); - } - #[Route('/', name: 'app_public_index')] public function index(): Response { @@ -260,19 +129,19 @@ class PublicController extends AbstractController // Préparer les données pour la carte $citiesForMap = []; - + foreach ($stats as $stat) { if ($stat->getZone() && $stat->getZone() !== 'undefined' && preg_match('/^\d+$/', $stat->getZone()) && $stat->getZone() !== '00000') { $cityName = $stat->getName() ?: $stat->getZone(); - + // Utiliser les coordonnées stockées si disponibles if ($stat->getLat() && $stat->getLon()) { $citiesForMap[] = [ 'name' => $cityName, 'zone' => $stat->getZone(), 'coordinates' => [ - 'lat' => (float)$stat->getLat(), - 'lon' => (float)$stat->getLon() + 'lat' => (float) $stat->getLat(), + 'lon' => (float) $stat->getLon() ], 'placesCount' => $stat->getPlacesCount(), 'completionPercent' => $stat->getCompletionPercent(), @@ -298,38 +167,38 @@ class PublicController extends AbstractController { // Cache simple pour éviter trop d'appels API $cacheKey = 'city_coords_' . $inseeCode; - + // Vérifier le cache (ici on utilise une approche simple) // En production, vous pourriez utiliser le cache Symfony - + $query = urlencode($cityName . ', France'); $url = "https://nominatim.openstreetmap.org/search?q={$query}&format=json&limit=1&countrycodes=fr"; - + try { // Ajouter un délai pour respecter les limites de l'API Nominatim usleep(100000); // 0.1 seconde entre les appels - + $context = stream_context_create([ 'http' => [ 'timeout' => 5, // Timeout de 5 secondes 'user_agent' => 'OSM-Commerces/1.0' ] ]); - + $response = file_get_contents($url, false, $context); - + if ($response === false) { error_log("DEBUG: Échec de récupération des coordonnées pour $cityName ($inseeCode)"); return null; } - + $data = json_decode($response, true); - + if (!empty($data) && isset($data[0]['lat']) && isset($data[0]['lon'])) { error_log("DEBUG: Coordonnées trouvées pour $cityName ($inseeCode): " . $data[0]['lat'] . ", " . $data[0]['lon']); return [ - 'lat' => (float)$data[0]['lat'], - 'lon' => (float)$data[0]['lon'] + 'lat' => (float) $data[0]['lat'], + 'lon' => (float) $data[0]['lon'] ]; } else { error_log("DEBUG: Aucune coordonnée trouvée pour $cityName ($inseeCode)"); @@ -337,7 +206,7 @@ class PublicController extends AbstractController } catch (\Exception $e) { error_log("DEBUG: Exception lors de la récupération des coordonnées pour $cityName ($inseeCode): " . $e->getMessage()); } - + return null; } @@ -400,7 +269,7 @@ class PublicController extends AbstractController { $this->actionLogger->log('dashboard', []); - + $stats_repo = $this->entityManager->getRepository(Stats::class)->findAll(); $stats_for_chart = []; @@ -429,74 +298,6 @@ class PublicController extends AbstractController ]); } - #[Route('/api/dashboard/regression', name: 'api_dashboard_regression', methods: ['POST'])] - public function saveRegressionData(Request $request): JsonResponse - { - $this->actionLogger->log('save_regression_data', []); - - // Récupérer les données de la requête - $data = json_decode($request->getContent(), true); - - if (!isset($data['angle']) || !isset($data['slope']) || !isset($data['intercept'])) { - return new JsonResponse([ - 'success' => false, - 'message' => 'Données de régression incomplètes' - ], Response::HTTP_BAD_REQUEST); - } - - // Récupérer les stats globales (zone 00000) - $statsGlobal = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => '00000']); - - if (!$statsGlobal) { - // Créer les stats globales si elles n'existent pas - $statsGlobal = new Stats(); - $statsGlobal->setZone('00000'); - $statsGlobal->setName('toutes les villes'); - $this->entityManager->persist($statsGlobal); - $this->entityManager->flush(); - } - - // Créer un nouveau followup pour la régression linéaire - $followup = new CityFollowUp(); - $followup->setName('regression_angle'); - $followup->setMeasure($data['angle']); - $followup->setDate(new \DateTime()); - $followup->setStats($statsGlobal); - - $this->entityManager->persist($followup); - - // Créer un followup pour la pente - $followupSlope = new CityFollowUp(); - $followupSlope->setName('regression_slope'); - $followupSlope->setMeasure($data['slope']); - $followupSlope->setDate(new \DateTime()); - $followupSlope->setStats($statsGlobal); - - $this->entityManager->persist($followupSlope); - - // Créer un followup pour l'ordonnée à l'origine - $followupIntercept = new CityFollowUp(); - $followupIntercept->setName('regression_intercept'); - $followupIntercept->setMeasure($data['intercept']); - $followupIntercept->setDate(new \DateTime()); - $followupIntercept->setStats($statsGlobal); - - $this->entityManager->persist($followupIntercept); - - $this->entityManager->flush(); - - return new JsonResponse([ - 'success' => true, - 'message' => 'Données de régression enregistrées avec succès', - 'followup' => [ - 'id' => $followup->getId(), - 'name' => $followup->getName(), - 'measure' => $followup->getMeasure(), - 'date' => $followup->getDate()->format('Y-m-d H:i:s') - ] - ]); - } - #[Route('/modify/{osm_object_id}/{version}/{changesetID}', name: 'app_public_submit')] public function submit($osm_object_id, $version, $changesetID): Response { @@ -857,7 +658,7 @@ class PublicController extends AbstractController public function publicFollowupGraph(string $insee_code, string $theme): Response { $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); - if (!$stats) { + if (!$stats) { $this->addFlash('error', '13 Aucune stats trouvée pour ce code INSEE.'); return $this->redirectToRoute('app_public_index'); } @@ -872,7 +673,7 @@ class PublicController extends AbstractController $followups = $stats->getCityFollowUps(); $countData = []; $completionData = []; - + foreach ($followups as $fu) { if ($fu->getName() === $theme . '_count') { $countData[] = [ @@ -912,7 +713,7 @@ class PublicController extends AbstractController if ($place) { return $this->redirectToRoute('app_public_edit', [ 'zipcode' => $place->getZipCode(), - 'name' => $place->getName() ? $place->getName() : '?', + 'name' => $place->getName() !== '' ? $place->getName() : '?', 'uuid' => $place->getUuidForUrl() ]); } else { @@ -1055,142 +856,4 @@ class PublicController extends AbstractController 'places_6mois' => $places_6mois, ]); } - - #[Route('/rss/demandes', name: 'app_public_rss_demandes')] - public function rssDemandes(): Response - { - $demandes = $this->entityManager->getRepository(Demande::class)->findAllOrderedByCreatedAt(); - - $content = $this->renderView('public/rss/demandes.xml.twig', [ - 'demandes' => $demandes, - 'base_url' => $this->getParameter('router.request_context.host'), - ]); - - $response = new Response($content); - $response->headers->set('Content-Type', 'application/rss+xml'); - - return $response; - } - - #[Route('/rss/city/{insee_code}/demandes', name: 'app_public_rss_city_demandes')] - public function rssCityDemandes(string $insee_code): Response - { - $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); - if (!$stats) { - throw $this->createNotFoundException('Ville non trouvée'); - } - - // Récupérer les demandes pour cette ville - $demandes = $this->entityManager->getRepository(Demande::class) - ->createQueryBuilder('d') - ->where('d.insee = :insee') - ->setParameter('insee', $insee_code) - ->orderBy('d.createdAt', 'DESC') - ->getQuery() - ->getResult(); - - $content = $this->renderView('public/rss/city_demandes.xml.twig', [ - 'demandes' => $demandes, - 'city' => $stats, - 'base_url' => $this->getParameter('router.request_context.host'), - ]); - - $response = new Response($content); - $response->headers->set('Content-Type', 'application/rss+xml'); - - return $response; - } - - #[Route('/rss/city/{insee_code}/themes', name: 'app_public_rss_city_themes')] - public function rssCityThemes(string $insee_code): Response - { - $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); - if (!$stats) { - throw $this->createNotFoundException('Ville non trouvée'); - } - - // Récupérer les changements thématiques pour cette ville - $followups = $stats->getCityFollowUps(); - $themeChanges = []; - - foreach ($followups as $followup) { - $name = $followup->getName(); - if (str_ends_with($name, '_count')) { - $type = substr($name, 0, -6); - if (!isset($themeChanges[$type])) { - $themeChanges[$type] = []; - } - $themeChanges[$type][] = $followup; - } - } - - // Trier les changements par date pour chaque thème - foreach ($themeChanges as &$changes) { - usort($changes, function ($a, $b) { - return $b->getDate() <=> $a->getDate(); - }); - } - - $content = $this->renderView('public/rss/city_themes.xml.twig', [ - 'themeChanges' => $themeChanges, - 'city' => $stats, - 'base_url' => $this->getParameter('router.request_context.host'), - 'followup_labels' => \App\Service\FollowUpService::getFollowUpThemes(), - ]); - - $response = new Response($content); - $response->headers->set('Content-Type', 'application/rss+xml'); - - return $response; - } - - #[Route('/city/{insee_code}/demandes', name: 'app_public_city_demandes')] - public function cityDemandes(string $insee_code): Response - { - $stats = $this->entityManager->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]); - if (!$stats) { - throw $this->createNotFoundException('Ville non trouvée'); - } - - // Récupérer les demandes pour cette ville - $demandes = $this->entityManager->getRepository(Demande::class) - ->createQueryBuilder('d') - ->where('d.insee = :insee') - ->setParameter('insee', $insee_code) - ->orderBy('d.createdAt', 'DESC') - ->getQuery() - ->getResult(); - - return $this->render('public/city_demandes.html.twig', [ - 'demandes' => $demandes, - 'city' => $stats, - ]); - } - - #[Route('/cities', name: 'app_public_cities')] - public function cities(): Response - { - $stats = $this->entityManager->getRepository(Stats::class)->findAll(); - - // Prepare data for the map - $citiesForMap = []; - foreach ($stats as $stat) { - if ($stat->getZone() !== 'undefined' && preg_match('/^\d+$/', $stat->getZone())) { - $citiesForMap[] = [ - 'name' => $stat->getName(), - 'zone' => $stat->getZone(), - 'lat' => $stat->getLat(), - 'lon' => $stat->getLon(), - 'placesCount' => $stat->getPlacesCount(), - 'completionPercent' => $stat->getCompletionPercent(), - ]; - } - } - - return $this->render('public/cities.html.twig', [ - 'stats' => $stats, - 'citiesForMap' => $citiesForMap, - 'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null, - ]); - } }