linking demandes

This commit is contained in:
Tykayn 2025-07-16 23:01:13 +02:00 committed by tykayn
parent 0aa050b38b
commit 7f79ec3a9f
6 changed files with 190 additions and 10 deletions

View file

@ -107,6 +107,7 @@ class LinkDemandesPlacesCommand extends Command
if (!$dryRun) { if (!$dryRun) {
$demande->setPlace($bestMatch); $demande->setPlace($bestMatch);
$demande->setPlaceUuid($bestMatch->getUuidForUrl());
$demande->setStatus('linked_to_place'); $demande->setStatus('linked_to_place');
$this->entityManager->persist($demande); $this->entityManager->persist($demande);
$linkedCount++; $linkedCount++;
@ -199,4 +200,4 @@ class LinkDemandesPlacesCommand extends Command
{ {
return str_replace(['%', '_'], ['\%', '\_'], $str); return str_replace(['%', '_'], ['\%', '\_'], $str);
} }
} }

View file

@ -0,0 +1,116 @@
<?php
namespace App\Command;
use App\Entity\Demande;
use App\Entity\Place;
use App\Repository\DemandeRepository;
use App\Repository\PlaceRepository;
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\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:link-demandes-places-osm',
description: 'Link Demandes to Places based on matching OSM type and ID',
)]
class LinkDemandesPlacesOsmCommand extends Command
{
private EntityManagerInterface $entityManager;
private DemandeRepository $demandeRepository;
private PlaceRepository $placeRepository;
public function __construct(
EntityManagerInterface $entityManager,
DemandeRepository $demandeRepository,
PlaceRepository $placeRepository
) {
parent::__construct();
$this->entityManager = $entityManager;
$this->demandeRepository = $demandeRepository;
$this->placeRepository = $placeRepository;
}
protected function configure(): void
{
$this
->addOption('dry-run', null, InputOption::VALUE_NONE, 'Show matches without linking');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Linking Demandes to Places based on matching OSM type and ID');
$dryRun = $input->getOption('dry-run');
// Find all Demandes without a UUID but with OSM type and ID
$demandesWithoutUuid = $this->demandeRepository->createQueryBuilder('d')
->where('d.placeUuid IS NULL')
->andWhere('d.osmObjectType IS NOT NULL')
->andWhere('d.osmId IS NOT NULL')
->getQuery()
->getResult();
if (empty($demandesWithoutUuid)) {
$io->warning('No Demandes without UUID but with OSM type and ID found.');
return Command::SUCCESS;
}
$io->info(sprintf('Found %d Demandes without UUID but with OSM type and ID.', count($demandesWithoutUuid)));
// Process each Demande
$linkedCount = 0;
/** @var Demande $demande */
foreach ($demandesWithoutUuid as $demande) {
$osmType = $demande->getOsmObjectType();
$osmId = $demande->getOsmId();
// Find Place with matching OSM type and ID
$place = $this->placeRepository->findOneBy([
'osm_kind' => $osmType,
'osmId' => $osmId
]);
if ($place) {
$io->text(sprintf(
'Match found: Demande #%d -> Place #%d (OSM %s/%d)',
$demande->getId(),
$place->getId(),
$osmType,
$osmId
));
if (!$dryRun) {
$demande->setPlace($place);
$demande->setPlaceUuid($place->getUuidForUrl());
$demande->setStatus('linked_to_place');
$this->entityManager->persist($demande);
$linkedCount++;
}
} else {
$io->text(sprintf(
'No matching Place found for Demande #%d (OSM %s/%d)',
$demande->getId(),
$osmType,
$osmId
));
}
}
if (!$dryRun && $linkedCount > 0) {
$this->entityManager->flush();
$io->success(sprintf('Linked %d Demandes to Places based on OSM type and ID.', $linkedCount));
} elseif ($dryRun) {
$io->info('Dry run completed. No changes were made.');
} else {
$io->info('No Demandes were linked to Places.');
}
return Command::SUCCESS;
}
}

View file

@ -151,6 +151,20 @@ class PublicController extends AbstractController
$demande->setOsmId((int)$data['osmId']); $demande->setOsmId((int)$data['osmId']);
} }
// Check if a Place exists with the same OSM ID and type
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);
}
}
$this->entityManager->persist($demande); $this->entityManager->persist($demande);
$this->entityManager->flush(); $this->entityManager->flush();

View file

@ -96,6 +96,7 @@
<th>Email</th> <th>Email</th>
<th>Date de création</th> <th>Date de création</th>
<th>Statut</th> <th>Statut</th>
<th>OSM</th>
<th>Place UUID</th> <th>Place UUID</th>
<th>Dernière tentative de contact</th> <th>Dernière tentative de contact</th>
<th>Actions</th> <th>Actions</th>
@ -105,7 +106,15 @@
{% for demande in demandes %} {% for demande in demandes %}
<tr> <tr>
<td>{{ demande.id }}</td> <td>{{ demande.id }}</td>
<td>{{ demande.query }}</td> <td>
{% if demande.placeUuid and demande.osmObjectType and demande.osmId %}
<a href="{{ path('app_public_edit_by_osm', {'osm_kind': demande.osmObjectType, 'osm_id': demande.osmId}) }}" title="Éditer la place">
{{ demande.query }}
</a>
{% else %}
{{ demande.query }}
{% endif %}
</td>
<td>{{ demande.email }}</td> <td>{{ demande.email }}</td>
<td>{{ demande.createdAt ? demande.createdAt|date('Y-m-d H:i:s') : '' }}</td> <td>{{ demande.createdAt ? demande.createdAt|date('Y-m-d H:i:s') : '' }}</td>
<td> <td>
@ -123,7 +132,28 @@
{{ demande.status }} {{ demande.status }}
</span> </span>
</td> </td>
<td>{{ demande.placeUuid }}</td> <td>
{% if demande.osmObjectType and demande.osmId %}
<a href="https://www.openstreetmap.org/{{ demande.osmObjectType }}/{{ demande.osmId }}" target="_blank" title="Voir sur OpenStreetMap">
<i class="bi bi-globe"></i> {{ demande.osmObjectType }}/{{ demande.osmId }}
</a>
{% else %}
-
{% endif %}
</td>
<td>
{% if demande.placeUuid %}
{% if demande.osmObjectType and demande.osmId %}
<a href="{{ path('app_public_edit_by_osm', {'osm_kind': demande.osmObjectType, 'osm_id': demande.osmId}) }}" title="Éditer la place">
{{ demande.placeUuid }}
</a>
{% else %}
{{ demande.placeUuid }}
{% endif %}
{% else %}
{{ demande.placeUuid }}
{% endif %}
</td>
<td>{{ demande.lastContactAttempt ? demande.lastContactAttempt|date('Y-m-d H:i:s') : '' }}</td> <td>{{ demande.lastContactAttempt ? demande.lastContactAttempt|date('Y-m-d H:i:s') : '' }}</td>
<td> <td>
<div class="btn-group" role="group"> <div class="btn-group" role="group">
@ -140,7 +170,7 @@
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="8" class="text-center">Aucune demande trouvée</td> <td colspan="9" class="text-center">Aucune demande trouvée</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View file

@ -508,18 +508,21 @@
// Mettre à jour les statistiques // Mettre à jour les statistiques
function updateStats() { function updateStats() {
if (countData.length > 0) { if (Array.isArray(countData) && countData.length > 0) {
const latestCount = countData[countData.length - 1]; const latestCount = countData[countData.length - 1];
document.getElementById('currentCount').textContent = latestCount.value; document.getElementById('currentCount').textContent = latestCount.value;
document.getElementById('lastUpdate').textContent = new Date(latestCount.date).toLocaleDateString('fr-FR'); document.getElementById('lastUpdate').textContent = new Date(latestCount.date).toLocaleDateString('fr-FR');
} }
if (completionData.length > 0) { if (Array.isArray(completionData) && completionData.length > 0) {
const latestCompletion = completionData[completionData.length - 1]; const latestCompletion = completionData[completionData.length - 1];
document.getElementById('currentCompletion').textContent = latestCompletion.value + '%'; document.getElementById('currentCompletion').textContent = latestCompletion.value + '%';
} }
document.getElementById('dataPoints').textContent = Math.max(countData.length, completionData.length); document.getElementById('dataPoints').textContent = Math.max(
Array.isArray(countData) ? countData.length : 0,
Array.isArray(completionData) ? completionData.length : 0
);
} }
// Configuration commune pour les graphiques // Configuration commune pour les graphiques
@ -571,7 +574,7 @@
datasets: [ datasets: [
{ {
label: "Nombre d'objets", label: "Nombre d'objets",
data: countData?.map(d => ({ x: new Date(d.date), y: d.value })), data: Array.isArray(countData) ? countData.map(d => ({ x: new Date(d.date), y: d.value })) : [],
borderColor: '#0d6efd', borderColor: '#0d6efd',
backgroundColor: 'rgba(13, 110, 253, 0.1)', backgroundColor: 'rgba(13, 110, 253, 0.1)',
borderWidth: 2, borderWidth: 2,
@ -581,7 +584,7 @@
}, },
{ {
label: 'Pourcentage de complétion', label: 'Pourcentage de complétion',
data: completionData?.map(d => ({ x: new Date(d.date), y: d.value })), data: Array.isArray(completionData) ? completionData.map(d => ({ x: new Date(d.date), y: d.value })) : [],
borderColor: '#198754', borderColor: '#198754',
backgroundColor: 'rgba(25, 135, 84, 0.1)', backgroundColor: 'rgba(25, 135, 84, 0.1)',
borderWidth: 2, borderWidth: 2,

View file

@ -47,6 +47,7 @@
<th>Nom du commerce</th> <th>Nom du commerce</th>
<th>Date de création</th> <th>Date de création</th>
<th>Statut</th> <th>Statut</th>
<th>OSM</th>
<th>Dernière tentative de contact</th> <th>Dernière tentative de contact</th>
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_ADMIN') %}
<th>Actions</th> <th>Actions</th>
@ -74,6 +75,21 @@
{{ demande.status }} {{ demande.status }}
</span> </span>
</td> </td>
<td>
{% if demande.osmObjectType and demande.osmId %}
<a href="https://www.openstreetmap.org/{{ demande.osmObjectType }}/{{ demande.osmId }}" target="_blank" title="Voir sur OpenStreetMap">
<i class="bi bi-globe"></i> {{ demande.osmObjectType }}/{{ demande.osmId }}
</a>
{% if demande.placeUuid %}
<br>
<a href="{{ path('app_public_edit_by_osm', {'osm_kind': demande.osmObjectType, 'osm_id': demande.osmId}) }}" title="Éditer la place">
<i class="bi bi-pencil-square"></i> Éditer
</a>
{% endif %}
{% else %}
-
{% endif %}
</td>
<td>{{ demande.lastContactAttempt ? demande.lastContactAttempt|date('Y-m-d H:i:s') : '' }}</td> <td>{{ demande.lastContactAttempt ? demande.lastContactAttempt|date('Y-m-d H:i:s') : '' }}</td>
{% if is_granted('ROLE_ADMIN') %} {% if is_granted('ROLE_ADMIN') %}
<td> <td>
@ -92,7 +108,7 @@
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="{% if is_granted('ROLE_ADMIN') %}6{% else %}5{% endif %}" class="text-center">Aucune demande trouvée pour cette ville</td> <td colspan="{% if is_granted('ROLE_ADMIN') %}7{% else %}6{% endif %}" class="text-center">Aucune demande trouvée pour cette ville</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>