mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-10-04 17:04:53 +02:00
retapage accueil, gestion de Demandes
This commit is contained in:
parent
d777221d0d
commit
f4c5e048ff
26 changed files with 2498 additions and 292 deletions
225
src/Command/UpdateMostRecentCityFollowupCommand.php
Normal file
225
src/Command/UpdateMostRecentCityFollowupCommand.php
Normal file
|
@ -0,0 +1,225 @@
|
|||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Stats;
|
||||
use App\Entity\CityFollowUp;
|
||||
use App\Service\Motocultrice;
|
||||
use App\Service\FollowUpService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:update-most-recent-city-followup',
|
||||
description: 'Met à jour les followups de la ville qui a une mesure récente qui date le plus, en priorisant les villes sans followup de type "rnb"'
|
||||
)]
|
||||
class UpdateMostRecentCityFollowupCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private Motocultrice $motocultrice,
|
||||
private FollowUpService $followUpService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setHelp('Cette commande trouve la ville avec la mesure la plus récente, en priorisant les villes sans followup de type "rnb", et met à jour ses followups. Elle nettoie d\'abord les doublons de Stats pour éviter les violations de contrainte unique.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->title('Mise à jour des followups pour la ville avec la mesure la plus récente');
|
||||
|
||||
// 1. Nettoyer les doublons de Stats pour éviter les violations de contrainte unique
|
||||
$io->section('Nettoyage des doublons de Stats');
|
||||
|
||||
$statsByZone = [];
|
||||
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||
$allStats = $statsRepo->findAll();
|
||||
|
||||
// Regrouper par code INSEE
|
||||
foreach ($allStats as $stat) {
|
||||
$zone = $stat->getZone();
|
||||
if (!$zone) continue;
|
||||
$statsByZone[$zone][] = $stat;
|
||||
}
|
||||
|
||||
$toDelete = [];
|
||||
$toKeep = [];
|
||||
foreach ($statsByZone as $zone => $statsList) {
|
||||
if (count($statsList) > 1) {
|
||||
// Trier par date_created (le plus ancien d'abord), puis par id si date absente
|
||||
usort($statsList, function($a, $b) {
|
||||
$da = $a->getDateCreated();
|
||||
$db = $b->getDateCreated();
|
||||
if ($da && $db) {
|
||||
return $da <=> $db;
|
||||
} elseif ($da) {
|
||||
return -1;
|
||||
} elseif ($db) {
|
||||
return 1;
|
||||
} else {
|
||||
return $a->getId() <=> $b->getId();
|
||||
}
|
||||
});
|
||||
// Garder le premier, supprimer les autres
|
||||
$toKeep[$zone] = $statsList[0];
|
||||
$toDelete[$zone] = array_slice($statsList, 1);
|
||||
}
|
||||
}
|
||||
|
||||
$totalToDelete = array_sum(array_map('count', $toDelete));
|
||||
if ($totalToDelete === 0) {
|
||||
$io->success('Aucun doublon trouvé.');
|
||||
} else {
|
||||
$io->info("$totalToDelete doublons de Stats trouvés à supprimer.");
|
||||
|
||||
foreach ($toDelete as $statsList) {
|
||||
foreach ($statsList as $stat) {
|
||||
$this->entityManager->remove($stat);
|
||||
}
|
||||
}
|
||||
$this->entityManager->flush();
|
||||
$io->success("$totalToDelete doublons supprimés avec succès.");
|
||||
}
|
||||
|
||||
// 2. Récupérer toutes les villes (Stats) sans utiliser les colonnes problématiques
|
||||
try {
|
||||
$allStats = $statsRepo->findAllCitiesWithoutLabourage();
|
||||
} catch (\Exception $e) {
|
||||
$io->error('Erreur lors de la récupération des villes: ' . $e->getMessage());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (empty($allStats)) {
|
||||
$io->error('Aucune ville trouvée dans la base de données.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->info('Nombre de villes trouvées: ' . count($allStats));
|
||||
|
||||
// 3. Identifier les villes sans followup de type "rnb"
|
||||
$citiesWithoutRnb = [];
|
||||
$citiesWithRnb = [];
|
||||
|
||||
foreach ($allStats as $stats) {
|
||||
$hasRnb = false;
|
||||
foreach ($stats->getCityFollowUps() as $followup) {
|
||||
if ($followup->getName() === 'rnb_count' || $followup->getName() === 'rnb_completion') {
|
||||
$hasRnb = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasRnb) {
|
||||
$citiesWithoutRnb[] = $stats;
|
||||
} else {
|
||||
$citiesWithRnb[] = $stats;
|
||||
}
|
||||
}
|
||||
|
||||
$io->info('Villes sans followup "rnb": ' . count($citiesWithoutRnb));
|
||||
$io->info('Villes avec followup "rnb": ' . count($citiesWithRnb));
|
||||
|
||||
// 4. Trouver la ville avec la mesure la plus récente
|
||||
$selectedCity = null;
|
||||
$latestDate = null;
|
||||
|
||||
// D'abord, chercher parmi les villes sans followup "rnb"
|
||||
if (!empty($citiesWithoutRnb)) {
|
||||
foreach ($citiesWithoutRnb as $stats) {
|
||||
$latestFollowup = $this->findLatestFollowup($stats);
|
||||
|
||||
if ($latestFollowup && ($latestDate === null || $latestFollowup->getDate() > $latestDate)) {
|
||||
$selectedCity = $stats;
|
||||
$latestDate = $latestFollowup->getDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si aucune ville sans followup "rnb" n'a été trouvée, chercher parmi toutes les villes
|
||||
if ($selectedCity === null) {
|
||||
foreach ($allStats as $stats) {
|
||||
$latestFollowup = $this->findLatestFollowup($stats);
|
||||
|
||||
if ($latestFollowup && ($latestDate === null || $latestFollowup->getDate() > $latestDate)) {
|
||||
$selectedCity = $stats;
|
||||
$latestDate = $latestFollowup->getDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($selectedCity === null) {
|
||||
$io->error('Aucune ville avec des followups n\'a été trouvée.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->section('Ville sélectionnée: ' . $selectedCity->getName() . ' (' . $selectedCity->getZone() . ')');
|
||||
if ($latestDate) {
|
||||
$io->info('Date de la dernière mesure: ' . $latestDate->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
// 5. Mettre à jour les followups pour la ville sélectionnée
|
||||
$io->section('Mise à jour des followups');
|
||||
|
||||
try {
|
||||
$this->followUpService->generateCityFollowUps(
|
||||
$selectedCity,
|
||||
$this->motocultrice,
|
||||
$this->entityManager
|
||||
);
|
||||
|
||||
$io->success('Followups mis à jour avec succès pour ' . $selectedCity->getName());
|
||||
|
||||
// Afficher les résultats
|
||||
$newFollowups = $selectedCity->getCityFollowUps();
|
||||
$io->section('Résultats');
|
||||
|
||||
$table = [];
|
||||
foreach ($newFollowups as $followup) {
|
||||
if ($followup->getDate() > (new \DateTime())->modify('-1 hour')) {
|
||||
$table[] = [
|
||||
$followup->getName(),
|
||||
$followup->getMeasure(),
|
||||
$followup->getDate()->format('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($table)) {
|
||||
$io->table(['Nom', 'Valeur', 'Date'], $table);
|
||||
} else {
|
||||
$io->info('Aucun nouveau followup généré dans la dernière heure.');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$io->error('Erreur lors de la mise à jour des followups: ' . $e->getMessage());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve le followup le plus récent pour une ville donnée
|
||||
*/
|
||||
private function findLatestFollowup(Stats $stats): ?CityFollowUp
|
||||
{
|
||||
$latestFollowup = null;
|
||||
|
||||
foreach ($stats->getCityFollowUps() as $followup) {
|
||||
if ($latestFollowup === null || $followup->getDate() > $latestFollowup->getDate()) {
|
||||
$latestFollowup = $followup;
|
||||
}
|
||||
}
|
||||
|
||||
return $latestFollowup;
|
||||
}
|
||||
}
|
174
src/Command/UpdateRecentCityFollowupCommand.php
Normal file
174
src/Command/UpdateRecentCityFollowupCommand.php
Normal file
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\Stats;
|
||||
use App\Entity\CityFollowUp;
|
||||
use App\Service\Motocultrice;
|
||||
use App\Service\FollowUpService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\DBAL\Exception\TableNotFoundException;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:update-recent-city-followup',
|
||||
description: 'Met à jour les followups de la ville qui a une mesure récente qui date le plus, en priorisant les villes sans followup de type "rnb"'
|
||||
)]
|
||||
class UpdateRecentCityFollowupCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private Motocultrice $motocultrice,
|
||||
private FollowUpService $followUpService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setHelp('Cette commande trouve la ville avec la mesure la plus récente, en priorisant les villes sans followup de type "rnb", et met à jour ses followups.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$io->title('Mise à jour des followups pour la ville avec la mesure la plus récente');
|
||||
|
||||
// 1. Récupérer toutes les villes (Stats) sans utiliser les colonnes problématiques
|
||||
$statsRepo = $this->entityManager->getRepository(Stats::class);
|
||||
try {
|
||||
$allStats = $statsRepo->findAllCitiesWithoutLabourage();
|
||||
} catch (\Exception $e) {
|
||||
$io->error('Erreur lors de la récupération des villes: ' . $e->getMessage());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (empty($allStats)) {
|
||||
$io->error('Aucune ville trouvée dans la base de données.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->info('Nombre de villes trouvées: ' . count($allStats));
|
||||
|
||||
// 2. Identifier les villes sans followup de type "rnb"
|
||||
$citiesWithoutRnb = [];
|
||||
$citiesWithRnb = [];
|
||||
|
||||
foreach ($allStats as $stats) {
|
||||
$hasRnb = false;
|
||||
foreach ($stats->getCityFollowUps() as $followup) {
|
||||
if ($followup->getName() === 'rnb_count' || $followup->getName() === 'rnb_completion') {
|
||||
$hasRnb = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$hasRnb) {
|
||||
$citiesWithoutRnb[] = $stats;
|
||||
} else {
|
||||
$citiesWithRnb[] = $stats;
|
||||
}
|
||||
}
|
||||
|
||||
$io->info('Villes sans followup "rnb": ' . count($citiesWithoutRnb));
|
||||
$io->info('Villes avec followup "rnb": ' . count($citiesWithRnb));
|
||||
|
||||
// 3. Trouver la ville avec la mesure la plus récente
|
||||
$selectedCity = null;
|
||||
$latestDate = null;
|
||||
|
||||
// D'abord, chercher parmi les villes sans followup "rnb"
|
||||
if (!empty($citiesWithoutRnb)) {
|
||||
foreach ($citiesWithoutRnb as $stats) {
|
||||
$latestFollowup = $this->findLatestFollowup($stats);
|
||||
|
||||
if ($latestFollowup && ($latestDate === null || $latestFollowup->getDate() > $latestDate)) {
|
||||
$selectedCity = $stats;
|
||||
$latestDate = $latestFollowup->getDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Si aucune ville sans followup "rnb" n'a été trouvée, chercher parmi toutes les villes
|
||||
if ($selectedCity === null) {
|
||||
foreach ($allStats as $stats) {
|
||||
$latestFollowup = $this->findLatestFollowup($stats);
|
||||
|
||||
if ($latestFollowup && ($latestDate === null || $latestFollowup->getDate() > $latestDate)) {
|
||||
$selectedCity = $stats;
|
||||
$latestDate = $latestFollowup->getDate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($selectedCity === null) {
|
||||
$io->error('Aucune ville avec des followups n\'a été trouvée.');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$io->section('Ville sélectionnée: ' . $selectedCity->getName() . ' (' . $selectedCity->getZone() . ')');
|
||||
if ($latestDate) {
|
||||
$io->info('Date de la dernière mesure: ' . $latestDate->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
// 4. Mettre à jour les followups pour la ville sélectionnée
|
||||
$io->section('Mise à jour des followups');
|
||||
|
||||
try {
|
||||
$this->followUpService->generateCityFollowUps(
|
||||
$selectedCity,
|
||||
$this->motocultrice,
|
||||
$this->entityManager
|
||||
);
|
||||
|
||||
$io->success('Followups mis à jour avec succès pour ' . $selectedCity->getName());
|
||||
|
||||
// Afficher les résultats
|
||||
$newFollowups = $selectedCity->getCityFollowUps();
|
||||
$io->section('Résultats');
|
||||
|
||||
$table = [];
|
||||
foreach ($newFollowups as $followup) {
|
||||
if ($followup->getDate() > (new \DateTime())->modify('-1 hour')) {
|
||||
$table[] = [
|
||||
$followup->getName(),
|
||||
$followup->getMeasure(),
|
||||
$followup->getDate()->format('Y-m-d H:i:s')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($table)) {
|
||||
$io->table(['Nom', 'Valeur', 'Date'], $table);
|
||||
} else {
|
||||
$io->info('Aucun nouveau followup généré dans la dernière heure.');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$io->error('Erreur lors de la mise à jour des followups: ' . $e->getMessage());
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve le followup le plus récent pour une ville donnée
|
||||
*/
|
||||
private function findLatestFollowup(Stats $stats): ?CityFollowUp
|
||||
{
|
||||
$latestFollowup = null;
|
||||
|
||||
foreach ($stats->getCityFollowUps() as $followup) {
|
||||
if ($latestFollowup === null || $followup->getDate() > $latestFollowup->getDate()) {
|
||||
$latestFollowup = $followup;
|
||||
}
|
||||
}
|
||||
|
||||
return $latestFollowup;
|
||||
}
|
||||
}
|
|
@ -489,7 +489,7 @@ final class AdminController extends AbstractController
|
|||
$progression7Days[$type] = \App\Service\FollowUpService::calculate7DayProgression($stats, $type);
|
||||
}
|
||||
$progression7Days['places'] = \App\Service\FollowUpService::calculate7DayProgression($stats, 'places');
|
||||
|
||||
|
||||
// --- Ajout : mesures CTC CityFollowUp pour le graphique d'évolution ---
|
||||
$ctc_completion_series = [];
|
||||
foreach ($stats->getCityFollowUps() as $fu) {
|
||||
|
@ -1908,4 +1908,164 @@ final class AdminController extends AbstractController
|
|||
'maptiler_token' => $_ENV['MAPTILER_TOKEN'] ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/demandes', name: 'app_admin_demandes')]
|
||||
public function listDemandes(Request $request): Response
|
||||
{
|
||||
$status = $request->query->get('status');
|
||||
|
||||
$repository = $this->entityManager->getRepository(\App\Entity\Demande::class);
|
||||
|
||||
if ($status) {
|
||||
$demandes = $repository->findByStatus($status);
|
||||
} else {
|
||||
$demandes = $repository->findAllOrderedByCreatedAt();
|
||||
}
|
||||
|
||||
// Get all possible statuses for the filter
|
||||
$allStatuses = ['new', 'email_provided', 'ready', 'email_sent', 'email_failed', 'email_opened', 'edit_form_opened', 'place_modified', 'linked_to_place'];
|
||||
|
||||
// Count demandes for each status
|
||||
$statusCounts = [];
|
||||
foreach ($allStatuses as $statusValue) {
|
||||
$statusCounts[$statusValue] = $repository->findByStatus($statusValue);
|
||||
}
|
||||
|
||||
// Get total count
|
||||
$totalCount = $repository->findAllOrderedByCreatedAt();
|
||||
|
||||
return $this->render('admin/demandes/list.html.twig', [
|
||||
'demandes' => $demandes,
|
||||
'current_status' => $status,
|
||||
'all_statuses' => $allStatuses,
|
||||
'status_counts' => $statusCounts,
|
||||
'total_count' => count($totalCount)
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/demandes/{id}/edit', name: 'app_admin_demande_edit')]
|
||||
public function editDemande(int $id, Request $request): Response
|
||||
{
|
||||
$demande = $this->entityManager->getRepository(\App\Entity\Demande::class)->find($id);
|
||||
|
||||
if (!$demande) {
|
||||
$this->addFlash('error', 'Demande non trouvée');
|
||||
return $this->redirectToRoute('app_admin_demandes');
|
||||
}
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$placeUuid = $request->request->get('placeUuid');
|
||||
|
||||
if ($placeUuid) {
|
||||
// Check if the Place exists
|
||||
$place = $this->entityManager->getRepository(Place::class)->findOneBy(['uuid_for_url' => $placeUuid]);
|
||||
|
||||
if ($place) {
|
||||
$demande->setPlaceUuid($placeUuid);
|
||||
$demande->setPlace($place);
|
||||
$demande->setStatus('linked_to_place');
|
||||
|
||||
// Set OSM object type and OSM ID from the Place
|
||||
$demande->setOsmObjectType($place->getOsmKind());
|
||||
$demande->setOsmId((int)$place->getOsmId());
|
||||
|
||||
$this->entityManager->persist($demande);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Demande mise à jour avec succès');
|
||||
} else {
|
||||
$this->addFlash('error', 'Place non trouvée avec cet UUID');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('admin/demandes/edit.html.twig', [
|
||||
'demande' => $demande
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/contacted-places', name: 'app_admin_contacted_places')]
|
||||
public function listContactedPlaces(): Response
|
||||
{
|
||||
$demandes = $this->entityManager->getRepository(\App\Entity\Demande::class)->findPlacesWithContactAttempt();
|
||||
|
||||
return $this->render('admin/demandes/contacted_places.html.twig', [
|
||||
'demandes' => $demandes
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/admin/demandes/{id}/send-email', name: 'app_admin_demande_send_email')]
|
||||
public function sendEmailToDemande(int $id, \Symfony\Component\Mailer\MailerInterface $mailer): Response
|
||||
{
|
||||
$demande = $this->entityManager->getRepository(\App\Entity\Demande::class)->find($id);
|
||||
|
||||
if (!$demande) {
|
||||
$this->addFlash('error', 'Demande non trouvée');
|
||||
return $this->redirectToRoute('app_admin_demandes');
|
||||
}
|
||||
|
||||
$place = $demande->getPlace();
|
||||
|
||||
if (!$place) {
|
||||
$this->addFlash('error', 'Aucune place associée à cette demande');
|
||||
return $this->redirectToRoute('app_admin_demande_edit', ['id' => $id]);
|
||||
}
|
||||
|
||||
// Check if the place has an email
|
||||
if (!$place->getEmail() && !$demande->getEmail()) {
|
||||
$this->addFlash('error', 'Aucun email associé à cette place ou à cette demande');
|
||||
return $this->redirectToRoute('app_admin_demande_edit', ['id' => $id]);
|
||||
}
|
||||
|
||||
// Use the email from the place if available, otherwise use the email from the demande
|
||||
$email = $place->getEmail() ?: $demande->getEmail();
|
||||
|
||||
// Generate the email content
|
||||
$emailContent = $this->renderView('admin/email_content.html.twig', [
|
||||
'place' => $place
|
||||
]);
|
||||
|
||||
// Only send the email in production environment
|
||||
if ($this->getParameter('kernel.environment') === 'prod') {
|
||||
$message = (new \Symfony\Component\Mime\Email())
|
||||
->from('contact@osm-commerce.fr')
|
||||
->to($email)
|
||||
->subject('Votre lien de modification OpenStreetMap')
|
||||
->html($emailContent);
|
||||
|
||||
try {
|
||||
$mailer->send($message);
|
||||
} catch (\Throwable $e) {
|
||||
$this->actionLogger->log('ERROR_envoi_email', [
|
||||
'demande_id' => $demande->getId(),
|
||||
'place_id' => $place->getId(),
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
$this->addFlash('error', 'Erreur lors de l\'envoi de l\'email : ' . $e->getMessage());
|
||||
return $this->redirectToRoute('app_admin_demande_edit', ['id' => $id]);
|
||||
}
|
||||
} else {
|
||||
// In non-production environments, just log the attempt
|
||||
$this->actionLogger->log('email_would_be_sent', [
|
||||
'demande_id' => $demande->getId(),
|
||||
'place_id' => $place->getId(),
|
||||
'email' => $email,
|
||||
'content' => $emailContent
|
||||
]);
|
||||
$this->addFlash('info', 'En environnement de production, un email serait envoyé à ' . $email);
|
||||
}
|
||||
|
||||
// Update the last contact attempt date and set status to email_sent
|
||||
$now = new \DateTime();
|
||||
$demande->setLastContactAttempt($now);
|
||||
$demande->setStatus('email_sent');
|
||||
$place->setLastContactAttemptDate($now);
|
||||
|
||||
$this->entityManager->persist($demande);
|
||||
$this->entityManager->persist($place);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->addFlash('success', 'Email envoyé avec succès');
|
||||
return $this->redirectToRoute('app_admin_contacted_places');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,69 @@ class FollowUpController extends AbstractController
|
|||
$this->followUpService = $followUpService;
|
||||
}
|
||||
|
||||
#[Route('/api/city-followup', name: 'api_city_followup', methods: ['POST'])]
|
||||
public function recordCityFollowUp(
|
||||
EntityManagerInterface $em,
|
||||
\Symfony\Component\HttpFoundation\Request $request
|
||||
): Response {
|
||||
$insee_code = $request->request->get('insee_code');
|
||||
$measure_label = $request->request->get('measure_label');
|
||||
$measure_value = $request->request->getFloat('measure_value');
|
||||
|
||||
if (!$insee_code || !$measure_label || $measure_value === null) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'Missing required parameters: insee_code, measure_label, measure_value'
|
||||
], Response::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$stats = $em->getRepository(Stats::class)->findOneBy(['zone' => $insee_code]);
|
||||
if (!$stats) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'No stats found for this INSEE code'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check if the same measure was recorded less than an hour ago
|
||||
$oneHourAgo = new \DateTime('-1 hour');
|
||||
$recentFollowUp = $em->getRepository(CityFollowUp::class)
|
||||
->findRecentByStatsAndName($stats, $measure_label, $oneHourAgo);
|
||||
|
||||
if ($recentFollowUp) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'message' => 'A measure with the same label was recorded less than an hour ago',
|
||||
'existing_measure' => [
|
||||
'id' => $recentFollowUp->getId(),
|
||||
'date' => $recentFollowUp->getDate()->format('Y-m-d H:i:s'),
|
||||
'value' => $recentFollowUp->getMeasure()
|
||||
]
|
||||
], Response::HTTP_TOO_MANY_REQUESTS);
|
||||
}
|
||||
|
||||
// Create and save the new follow-up
|
||||
$followUp = new CityFollowUp();
|
||||
$followUp->setName($measure_label);
|
||||
$followUp->setMeasure($measure_value);
|
||||
$followUp->setDate(new \DateTime());
|
||||
$followUp->setStats($stats);
|
||||
|
||||
$em->persist($followUp);
|
||||
$em->flush();
|
||||
|
||||
return $this->json([
|
||||
'success' => true,
|
||||
'message' => 'City follow-up recorded successfully',
|
||||
'follow_up' => [
|
||||
'id' => $followUp->getId(),
|
||||
'name' => $followUp->getName(),
|
||||
'measure' => $followUp->getMeasure(),
|
||||
'date' => $followUp->getDate()->format('Y-m-d H:i:s')
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
#[Route('/admin/followup/{insee_code}/delete', name: 'admin_followup_delete', requirements: ['insee_code' => '\\d+'])]
|
||||
public function deleteFollowups(string $insee_code, EntityManagerInterface $em): Response {
|
||||
|
|
|
@ -4,16 +4,21 @@ namespace App\Controller;
|
|||
|
||||
use App\Entity\Stats;
|
||||
use App\Entity\Place;
|
||||
use App\Entity\CityFollowUp;
|
||||
use App\Entity\Demande;
|
||||
use App\Service\Motocultrice;
|
||||
use App\Service\FollowUpService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use GuzzleHttp\Client;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Symfony\Component\Mailer\MailerInterface;
|
||||
use App\Service\ActionLogger;
|
||||
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
|
||||
|
||||
class PublicController extends AbstractController
|
||||
{
|
||||
|
@ -24,7 +29,8 @@ class PublicController extends AbstractController
|
|||
private EntityManagerInterface $entityManager,
|
||||
private Motocultrice $motocultrice,
|
||||
private MailerInterface $mailer,
|
||||
private ActionLogger $actionLogger
|
||||
private ActionLogger $actionLogger,
|
||||
private FollowUpService $followUpService
|
||||
) {}
|
||||
|
||||
#[Route('/propose-email/{email}/{type}/{id}', name: 'app_public_propose_email')]
|
||||
|
@ -115,6 +121,73 @@ class PublicController extends AbstractController
|
|||
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((int)$data['osmId']);
|
||||
}
|
||||
|
||||
$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
|
||||
{
|
||||
|
@ -122,11 +195,11 @@ 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[] = [
|
||||
|
@ -160,33 +233,33 @@ 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 [
|
||||
|
@ -199,7 +272,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;
|
||||
}
|
||||
|
||||
|
@ -262,7 +335,7 @@ class PublicController extends AbstractController
|
|||
{
|
||||
|
||||
$this->actionLogger->log('dashboard', []);
|
||||
|
||||
|
||||
$stats_repo = $this->entityManager->getRepository(Stats::class)->findAll();
|
||||
|
||||
$stats_for_chart = [];
|
||||
|
@ -291,6 +364,74 @@ 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
|
||||
{
|
||||
|
@ -666,7 +807,7 @@ class PublicController extends AbstractController
|
|||
$followups = $stats->getCityFollowUps();
|
||||
$countData = [];
|
||||
$completionData = [];
|
||||
|
||||
|
||||
foreach ($followups as $fu) {
|
||||
if ($fu->getName() === $theme . '_count') {
|
||||
$countData[] = [
|
||||
|
@ -849,4 +990,47 @@ 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('/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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
177
src/Entity/Demande.php
Normal file
177
src/Entity/Demande.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\DemandeRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: DemandeRepository::class)]
|
||||
class Demande
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $query = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $email = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?int $insee = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
|
||||
private ?\DateTimeInterface $createdAt = null;
|
||||
|
||||
#[ORM\Column(length: 50)]
|
||||
private ?string $status = 'new';
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $placeUuid = null;
|
||||
|
||||
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
|
||||
private ?\DateTimeInterface $lastContactAttempt = null;
|
||||
|
||||
#[ORM\ManyToOne]
|
||||
private ?Place $place = null;
|
||||
|
||||
#[ORM\Column(length: 10, nullable: true)]
|
||||
private ?string $osmObjectType = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?int $osmId = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->createdAt = new \DateTime();
|
||||
$this->status = 'new';
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getQuery(): ?string
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
|
||||
public function setQuery(string $query): static
|
||||
{
|
||||
$this->query = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getEmail(): ?string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function setEmail(?string $email): static
|
||||
{
|
||||
$this->email = $email;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInsee(): ?int
|
||||
{
|
||||
return $this->insee;
|
||||
}
|
||||
|
||||
public function setInsee(?int $insee): static
|
||||
{
|
||||
$this->insee = $insee;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(\DateTimeInterface $createdAt): static
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStatus(): ?string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(string $status): static
|
||||
{
|
||||
$this->status = $status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPlaceUuid(): ?string
|
||||
{
|
||||
return $this->placeUuid;
|
||||
}
|
||||
|
||||
public function setPlaceUuid(?string $placeUuid): static
|
||||
{
|
||||
$this->placeUuid = $placeUuid;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLastContactAttempt(): ?\DateTimeInterface
|
||||
{
|
||||
return $this->lastContactAttempt;
|
||||
}
|
||||
|
||||
public function setLastContactAttempt(?\DateTimeInterface $lastContactAttempt): static
|
||||
{
|
||||
$this->lastContactAttempt = $lastContactAttempt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPlace(): ?Place
|
||||
{
|
||||
return $this->place;
|
||||
}
|
||||
|
||||
public function setPlace(?Place $place): static
|
||||
{
|
||||
$this->place = $place;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOsmObjectType(): ?string
|
||||
{
|
||||
return $this->osmObjectType;
|
||||
}
|
||||
|
||||
public function setOsmObjectType(?string $osmObjectType): static
|
||||
{
|
||||
$this->osmObjectType = $osmObjectType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOsmId(): ?int
|
||||
{
|
||||
return $this->osmId;
|
||||
}
|
||||
|
||||
public function setOsmId(?int $osmId): static
|
||||
{
|
||||
$this->osmId = $osmId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -16,28 +16,20 @@ class CityFollowUpRepository extends ServiceEntityRepository
|
|||
parent::__construct($registry, CityFollowUp::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return CityFollowUp[] Returns an array of CityFollowUp objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('c')
|
||||
// ->andWhere('c.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('c.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?CityFollowUp
|
||||
// {
|
||||
// return $this->createQueryBuilder('c')
|
||||
// ->andWhere('c.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
public function findRecentByStatsAndName(Stats $stats, string $name, \DateTime $since): ?CityFollowUp
|
||||
{
|
||||
return $this->createQueryBuilder('c')
|
||||
->andWhere('c.stats = :stats')
|
||||
->andWhere('c.name = :name')
|
||||
->andWhere('c.date >= :since')
|
||||
->setParameter('stats', $stats)
|
||||
->setParameter('name', $name)
|
||||
->setParameter('since', $since)
|
||||
->orderBy('c.date', 'DESC')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
99
src/Repository/DemandeRepository.php
Normal file
99
src/Repository/DemandeRepository.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\Demande;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<Demande>
|
||||
*/
|
||||
class DemandeRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, Demande::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the most recent Demande by query (business name)
|
||||
*/
|
||||
public function findMostRecentByQuery(string $query): ?Demande
|
||||
{
|
||||
return $this->createQueryBuilder('d')
|
||||
->andWhere('d.query = :query')
|
||||
->setParameter('query', $query)
|
||||
->orderBy('d.createdAt', 'DESC')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find Demandes without an email
|
||||
*/
|
||||
public function findWithoutEmail(): array
|
||||
{
|
||||
return $this->createQueryBuilder('d')
|
||||
->andWhere('d.email IS NULL')
|
||||
->orderBy('d.createdAt', 'DESC')
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find Demandes by status
|
||||
*/
|
||||
public function findByStatus(string $status): array
|
||||
{
|
||||
return $this->createQueryBuilder('d')
|
||||
->andWhere('d.status = :status')
|
||||
->setParameter('status', $status)
|
||||
->orderBy('d.createdAt', 'DESC')
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find Demandes with a Place UUID
|
||||
*/
|
||||
public function findWithPlaceUuid(): array
|
||||
{
|
||||
return $this->createQueryBuilder('d')
|
||||
->andWhere('d.placeUuid IS NOT NULL')
|
||||
->orderBy('d.createdAt', 'DESC')
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all Demandes in reverse chronological order
|
||||
*/
|
||||
public function findAllOrderedByCreatedAt(): array
|
||||
{
|
||||
return $this->createQueryBuilder('d')
|
||||
->orderBy('d.createdAt', 'DESC')
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find Places that have been contacted, ordered by last contact attempt
|
||||
*/
|
||||
public function findPlacesWithContactAttempt(): array
|
||||
{
|
||||
return $this->createQueryBuilder('d')
|
||||
->andWhere('d.lastContactAttempt IS NOT NULL')
|
||||
->andWhere('d.place IS NOT NULL')
|
||||
->orderBy('d.lastContactAttempt', 'DESC')
|
||||
->getQuery()
|
||||
->getResult()
|
||||
;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,42 @@ class StatsRepository extends ServiceEntityRepository
|
|||
parent::__construct($registry, Stats::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all cities without using problematic columns
|
||||
*
|
||||
* @return Stats[] Returns an array of Stats objects
|
||||
*/
|
||||
public function findAllCitiesWithoutLabourage(): array
|
||||
{
|
||||
// Use native SQL to avoid ORM mapping issues with missing columns
|
||||
$conn = $this->getEntityManager()->getConnection();
|
||||
$sql = '
|
||||
SELECT id, zone, completion_percent, places_count, avec_horaires,
|
||||
avec_adresse, avec_site, avec_accessibilite, avec_note,
|
||||
name, population, siren, code_epci, codes_postaux,
|
||||
date_created, date_modified, avec_siret, avec_name,
|
||||
osm_data_date_min, osm_data_date_avg, osm_data_date_max,
|
||||
budget_annuel, lat, lon
|
||||
FROM stats
|
||||
WHERE zone != :global_zone
|
||||
';
|
||||
$stmt = $conn->prepare($sql);
|
||||
$resultSet = $stmt->executeQuery(['global_zone' => '00000']);
|
||||
|
||||
$results = $resultSet->fetchAllAssociative();
|
||||
|
||||
// Get existing Stats entities by ID
|
||||
$statsEntities = [];
|
||||
foreach ($results as $row) {
|
||||
$stats = $this->find($row['id']);
|
||||
if ($stats) {
|
||||
$statsEntities[] = $stats;
|
||||
}
|
||||
}
|
||||
|
||||
return $statsEntities;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return Stats[] Returns an array of Stats objects
|
||||
// */
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue