2025-07-14 18:17:41 +02:00
< ? php
namespace App\Command ;
use App\Entity\Stats ;
use App\Entity\Place ;
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 ;
use App\Service\Motocultrice ;
use App\Service\FollowUpService ;
#[AsCommand(
name : 'app:process-labourage-queue' ,
description : 'Traite la file d\'attente de labourage différé des villes (cron)'
)]
class ProcessLabourageQueueCommand extends Command
{
public function __construct (
private EntityManagerInterface $entityManager ,
private Motocultrice $motocultrice ,
private FollowUpService $followUpService
) {
parent :: __construct ();
}
protected function execute ( InputInterface $input , OutputInterface $output ) : int
{
$io = new SymfonyStyle ( $input , $output );
// Sélectionner la Stats à traiter (date_labourage_requested la plus ancienne, non traitée ou à refaire)
$stats = $this -> entityManager -> getRepository ( Stats :: class )
-> createQueryBuilder ( 's' )
-> where ( 's.date_labourage_requested IS NOT NULL' )
-> andWhere ( 's.date_labourage_done IS NULL OR s.date_labourage_done < s.date_labourage_requested' )
-> andWhere ( 's.zone != :global_zone' )
-> setParameter ( 'global_zone' , '00000' )
-> orderBy ( 's.date_labourage_requested' , 'ASC' )
-> setMaxResults ( 1 )
-> getQuery ()
-> getOneOrNullResult ();
if ( ! $stats ) {
// 1. Villes jamais labourées (date_labourage_done NULL, hors 00000)
$stats = $this -> entityManager -> getRepository ( Stats :: class )
-> createQueryBuilder ( 's' )
-> where ( 's.zone != :global_zone' )
-> andWhere ( 's.date_labourage_done IS NULL' )
-> setParameter ( 'global_zone' , '00000' )
-> orderBy ( 's.date_modified' , 'ASC' )
-> setMaxResults ( 1 )
-> getQuery ()
-> getOneOrNullResult ();
if ( $stats ) {
$io -> note ( 'Aucune ville en attente, on traite en priorité une ville jamais labourée : ' . $stats -> getName () . ' (' . $stats -> getZone () . ')' );
$stats -> setDateLabourageRequested ( new \DateTime ());
$this -> entityManager -> persist ( $stats );
$this -> entityManager -> flush ();
} else {
// 2. Ville la plus anciennement modifiée (hors 00000)
$stats = $this -> entityManager -> getRepository ( Stats :: class )
-> createQueryBuilder ( 's' )
-> where ( 's.zone != :global_zone' )
-> setParameter ( 'global_zone' , '00000' )
-> orderBy ( 's.date_modified' , 'ASC' )
-> setMaxResults ( 1 )
-> getQuery ()
-> getOneOrNullResult ();
if ( ! $stats ) {
$io -> success ( 'Aucune ville à traiter.' );
return Command :: SUCCESS ;
}
$io -> note ( 'Aucune ville en attente, on demande le labourage de la ville la plus anciennement modifiée : ' . $stats -> getName () . ' (' . $stats -> getZone () . ')' );
$stats -> setDateLabourageRequested ( new \DateTime ());
$this -> entityManager -> persist ( $stats );
$this -> entityManager -> flush ();
}
}
$io -> section ( 'Traitement de la ville : ' . $stats -> getName () . ' (' . $stats -> getZone () . ')' );
// Vérifier la RAM disponible (>= 1 Go)
$meminfo = @ file_get_contents ( '/proc/meminfo' );
$ram_ok = false ;
if ( $meminfo !== false && preg_match ( '/^MemAvailable:\s+(\d+)/m' , $meminfo , $matches )) {
$mem_kb = ( int ) $matches [ 1 ];
$ram_ok = ( $mem_kb >= 1024 * 1024 ); // 1 Go
}
if ( ! $ram_ok ) {
$io -> warning ( 'RAM insuffisante, on attend le prochain cron.' );
return Command :: SUCCESS ;
}
// Effectuer le labourage complet (reprendre la logique de création/màj des objets Place)
$io -> info ( 'RAM suffisante, lancement du labourage...' );
$places_overpass = $this -> motocultrice -> labourer ( $stats -> getZone ());
$processedCount = 0 ;
$updatedCount = 0 ;
$existingPlacesQuery = $this -> entityManager -> getRepository ( Place :: class )
-> createQueryBuilder ( 'p' )
-> select ( 'p.osmId, p.osm_kind, p.id' )
-> where ( 'p.zip_code = :zip_code' )
-> setParameter ( 'zip_code' , $stats -> getZone ())
-> getQuery ();
$existingPlacesResult = $existingPlacesQuery -> getResult ();
$placesByOsmKey = [];
foreach ( $existingPlacesResult as $placeData ) {
$osmKey = $placeData [ 'osm_kind' ] . '_' . $placeData [ 'osmId' ];
$placesByOsmKey [ $osmKey ] = $placeData [ 'id' ];
}
foreach ( $places_overpass as $placeData ) {
$osmKey = $placeData [ 'type' ] . '_' . $placeData [ 'id' ];
$existingPlaceId = $placesByOsmKey [ $osmKey ] ? ? null ;
if ( ! $existingPlaceId ) {
$place = new Place ();
$place -> setOsmId ( $placeData [ 'id' ])
-> setOsmKind ( $placeData [ 'type' ])
-> setZipCode ( $stats -> getZone ())
-> 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 )
-> setPlaceCount ( 0 );
$place -> update_place_from_overpass_data ( $placeData );
$this -> entityManager -> persist ( $place );
$stats -> addPlace ( $place );
$processedCount ++ ;
} else {
$existingPlace = $this -> entityManager -> getRepository ( Place :: class ) -> find ( $existingPlaceId );
if ( $existingPlace ) {
$existingPlace -> setDead ( false );
$existingPlace -> update_place_from_overpass_data ( $placeData );
$stats -> addPlace ( $existingPlace );
$this -> entityManager -> persist ( $existingPlace );
$updatedCount ++ ;
}
}
}
$stats -> setDateLabourageDone ( new \DateTime ());
2025-08-02 15:14:13 +02:00
// Update city name from API if available
$apiCityName = $this -> motocultrice -> get_city_osm_from_zip_code ( $stats -> getZone ());
if ( $apiCityName && $apiCityName !== $stats -> getName ()) {
$io -> info ( sprintf ( 'Updating city name from "%s" to "%s" based on API data' , $stats -> getName (), $apiCityName ));
$stats -> setName ( $apiCityName );
}
2025-08-02 11:22:09 +02:00
$io -> info ( 'Récupération des followups de cette ville...' );
// $this->followUpService->generateCityFollowUps($stats, $this->motocultrice, $this->entityManager);
2025-08-02 10:59:49 +02:00
// update completion
$stats -> computeCompletionPercent ();
$io -> info ( 'Pourcentage de complétion: ' . $stats -> getCompletionPercent ());
$this -> entityManager -> persist ( $stats );
$this -> entityManager -> flush ();
2025-07-14 18:17:41 +02:00
$io -> success ( " Labourage terminé : $processedCount nouveaux lieux, $updatedCount lieux mis à jour. " );
return Command :: SUCCESS ;
}
}