- Ajout système complet de gestion des secteurs avec contours géographiques - Import des contours départementaux depuis GeoJSON - API REST pour la gestion des secteurs (/api/sectors) - Service de géolocalisation pour déterminer les secteurs - Migration base de données avec tables x_departements_contours et sectors_adresses - Interface Flutter pour visualisation et gestion des secteurs - Ajout thème sombre dans l'application - Corrections diverses et optimisations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1307 lines
60 KiB
PHP
1307 lines
60 KiB
PHP
<?php
|
|
namespace App\Controllers;
|
|
|
|
use Database;
|
|
use Response;
|
|
use LogService;
|
|
use ApiService;
|
|
use AddressService;
|
|
use DepartmentBoundaryService;
|
|
|
|
require_once __DIR__ . '/../Services/ApiService.php';
|
|
require_once __DIR__ . '/../Services/AddressService.php';
|
|
require_once __DIR__ . '/../Services/DepartmentBoundaryService.php';
|
|
|
|
class SectorController
|
|
{
|
|
private \PDO $db;
|
|
private LogService $logService;
|
|
private AddressService $addressService;
|
|
private DepartmentBoundaryService $boundaryService;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->db = Database::getInstance();
|
|
$this->logService = new LogService();
|
|
$this->addressService = new AddressService();
|
|
$this->boundaryService = new DepartmentBoundaryService();
|
|
}
|
|
|
|
/**
|
|
* GET /sectors - Récupérer tous les secteurs
|
|
*/
|
|
public function index(): void
|
|
{
|
|
try {
|
|
$entityId = $_SESSION['entity_id'] ?? null;
|
|
|
|
if (!$entityId) {
|
|
Response::json(['status' => 'error', 'message' => 'Entité non définie'], 400);
|
|
return;
|
|
}
|
|
|
|
$query = "
|
|
SELECT
|
|
s.id,
|
|
s.libelle,
|
|
s.color,
|
|
s.sector,
|
|
o.fk_entite,
|
|
GROUP_CONCAT(ous.fk_user) as membres
|
|
FROM ope_sectors s
|
|
LEFT JOIN ope_users_sectors ous ON s.id = ous.fk_sector
|
|
JOIN operations o ON s.fk_operation = o.id
|
|
WHERE o.fk_entite = :entity_id
|
|
GROUP BY s.id
|
|
ORDER BY s.libelle
|
|
";
|
|
|
|
$stmt = $this->db->prepare($query);
|
|
$stmt->execute(['entity_id' => $entityId]);
|
|
$sectors = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
|
|
// Convertir les membres en tableau d'entiers
|
|
foreach ($sectors as &$sector) {
|
|
if ($sector['membres']) {
|
|
$sector['membres'] = array_map('intval', explode(',', $sector['membres']));
|
|
} else {
|
|
$sector['membres'] = [];
|
|
}
|
|
}
|
|
|
|
Response::json(['status' => 'success', 'data' => $sectors]);
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logService->error('Erreur lors de la récupération des secteurs', [
|
|
'error' => $e->getMessage(),
|
|
'trace' => $e->getTraceAsString()
|
|
]);
|
|
Response::json(['status' => 'error', 'message' => 'Erreur lors de la récupération des secteurs'], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /sectors - Créer un nouveau secteur
|
|
*/
|
|
public function create(): void
|
|
{
|
|
try {
|
|
$data = json_decode(file_get_contents('php://input'), true);
|
|
|
|
// Validation des données - Structure directe depuis Flutter
|
|
if (!isset($data['libelle']) || !isset($data['color']) || !isset($data['sector'])) {
|
|
Response::json(['status' => 'error', 'message' => 'Données du secteur manquantes'], 400);
|
|
return;
|
|
}
|
|
|
|
$sectorData = $data;
|
|
$entityId = $data['fk_entite'] ?? $_SESSION['entity_id'] ?? null;
|
|
$operationId = $data['operation_id'] ?? null;
|
|
$userId = $data['user_id'] ?? $_SESSION['user_id'] ?? null;
|
|
$users = $data['users'] ?? [];
|
|
|
|
if (!$entityId) {
|
|
Response::json(['status' => 'error', 'message' => 'Entité non définie'], 400);
|
|
return;
|
|
}
|
|
|
|
// Validation de l'opération
|
|
if (!$operationId) {
|
|
Response::json(['status' => 'error', 'message' => 'Opération non définie'], 400);
|
|
return;
|
|
}
|
|
|
|
// Traitement des coordonnées pour validation
|
|
$sector = $sectorData['sector'];
|
|
$points = explode('#', rtrim($sector, '#'));
|
|
$coordinates = [];
|
|
|
|
foreach ($points as $point) {
|
|
if (!empty($point)) {
|
|
list($lat, $lng) = explode('/', $point);
|
|
$coordinates[] = [floatval($lat), floatval($lng)]; // Format [lat, lng] pour AddressService
|
|
}
|
|
}
|
|
|
|
if (count($coordinates) < 3) {
|
|
Response::json(['status' => 'error', 'message' => 'Un secteur doit avoir au moins 3 points'], 400);
|
|
return;
|
|
}
|
|
|
|
// Vérifier que le secteur est dans le département de l'entité
|
|
try {
|
|
// Récupérer le code postal de l'entité pour en déduire le département
|
|
$deptQuery = "SELECT code_postal, encrypted_name FROM entites WHERE id = :entity_id";
|
|
$deptStmt = $this->db->prepare($deptQuery);
|
|
$deptStmt->execute(['entity_id' => $entityId]);
|
|
$entity = $deptStmt->fetch();
|
|
|
|
if (!$entity || !$entity['code_postal']) {
|
|
Response::json(['status' => 'error', 'message' => 'Code postal de l\'entité non défini'], 400);
|
|
return;
|
|
}
|
|
|
|
// Extraire le département du code postal (2 premiers caractères)
|
|
$codePostal = $entity['code_postal'];
|
|
if (strlen($codePostal) === 4) {
|
|
$codePostal = '0' . $codePostal; // Ajouter le 0 devant si nécessaire
|
|
}
|
|
$departement = substr($codePostal, 0, 2);
|
|
|
|
// Identifier tous les départements touchés par le secteur
|
|
$departmentsTouched = $this->boundaryService->getDepartmentsForSector($coordinates);
|
|
|
|
if (empty($departmentsTouched)) {
|
|
$this->logService->warning('Aucun département trouvé pour le secteur', [
|
|
'libelle' => $data['libelle'],
|
|
'entity_id' => $entityId,
|
|
'entity_dept' => $departement
|
|
]);
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->logService->warning('Impossible de vérifier les limites départementales', [
|
|
'error' => $e->getMessage(),
|
|
'libelle' => $data['libelle']
|
|
]);
|
|
}
|
|
|
|
// Récupérer les adresses dans le polygone
|
|
try {
|
|
$addressCount = $this->addressService->countAddressesInPolygon($coordinates, $entityId);
|
|
} catch (\Exception $e) {
|
|
$this->logService->warning('Impossible de récupérer les adresses du secteur', [
|
|
'error' => $e->getMessage(),
|
|
'libelle' => $data['libelle'],
|
|
'entity_id' => $entityId
|
|
]);
|
|
$addressCount = 0;
|
|
}
|
|
|
|
$this->db->beginTransaction();
|
|
|
|
// Insertion du secteur
|
|
$query = "INSERT INTO ope_sectors (libelle, color, sector, fk_operation) VALUES (:libelle, :color, :sector, :operation_id)";
|
|
$stmt = $this->db->prepare($query);
|
|
$stmt->execute([
|
|
'libelle' => $sectorData['libelle'],
|
|
'color' => $sectorData['color'],
|
|
'sector' => $sectorData['sector'],
|
|
'operation_id' => $operationId
|
|
]);
|
|
|
|
$sectorId = $this->db->lastInsertId();
|
|
|
|
// Affectation des users si fournis
|
|
if (!empty($users)) {
|
|
$queryMember = "INSERT INTO ope_users_sectors (fk_operation, fk_user, fk_sector, created_at, fk_user_creat, chk_active)
|
|
VALUES (:operation_id, :user_id, :sector_id, NOW(), :user_creat, 1)";
|
|
$stmtMember = $this->db->prepare($queryMember);
|
|
|
|
foreach ($users as $memberId) {
|
|
$stmtMember->execute([
|
|
'operation_id' => $operationId,
|
|
'user_id' => $memberId,
|
|
'sector_id' => $sectorId,
|
|
'user_creat' => $userId
|
|
]);
|
|
}
|
|
}
|
|
|
|
// D'abord, chercher et intégrer les passages orphelins (fk_sector=NULL) dans le nouveau secteur
|
|
$passagesIntegrated = 0;
|
|
$addressesToExclude = []; // Adresses à ne pas créer en doublon
|
|
|
|
try {
|
|
// Créer le polygone SQL pour la vérification
|
|
$polygonPoints = [];
|
|
foreach ($coordinates as $coord) {
|
|
$polygonPoints[] = $coord[1] . ' ' . $coord[0]; // longitude latitude
|
|
}
|
|
$polygonPoints[] = $polygonPoints[0]; // Fermer le polygone
|
|
$polygonString = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
|
|
|
// Chercher les passages orphelins dans le périmètre du secteur
|
|
$orphanQuery = "SELECT id, fk_adresse, gps_lat, gps_lng
|
|
FROM ope_pass
|
|
WHERE fk_operation = :operation_id
|
|
AND fk_sector IS NULL
|
|
AND gps_lat IS NOT NULL
|
|
AND gps_lng IS NOT NULL
|
|
AND ST_Contains(ST_GeomFromText(:polygon, 4326),
|
|
POINT(CAST(gps_lng AS DECIMAL(10,8)),
|
|
CAST(gps_lat AS DECIMAL(10,8))))";
|
|
|
|
$orphanStmt = $this->db->prepare($orphanQuery);
|
|
$orphanStmt->execute([
|
|
'operation_id' => $operationId,
|
|
'polygon' => $polygonString
|
|
]);
|
|
|
|
$orphanPassages = $orphanStmt->fetchAll();
|
|
|
|
if (!empty($orphanPassages)) {
|
|
$updateOrphanQuery = "UPDATE ope_pass SET fk_sector = :sector_id WHERE id = :passage_id";
|
|
$updateOrphanStmt = $this->db->prepare($updateOrphanQuery);
|
|
|
|
foreach ($orphanPassages as $orphan) {
|
|
$updateOrphanStmt->execute([
|
|
'sector_id' => $sectorId,
|
|
'passage_id' => $orphan['id']
|
|
]);
|
|
$passagesIntegrated++;
|
|
|
|
// Si le passage a un fk_adresse, l'ajouter à la liste des exclusions
|
|
if (!empty($orphan['fk_adresse'])) {
|
|
$addressesToExclude[] = $orphan['fk_adresse'];
|
|
}
|
|
}
|
|
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->logService->warning('Erreur lors de la récupération des passages orphelins', [
|
|
'sector_id' => $sectorId,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
|
|
// Stocker les adresses du secteur
|
|
$passagesCreated = 0; // Initialiser le compteur de passages
|
|
try {
|
|
$addresses = $this->addressService->getAddressesInPolygon($coordinates, $entityId);
|
|
|
|
if (!empty($addresses)) {
|
|
$queryAddress = "INSERT INTO sectors_adresses (fk_sector, fk_adresse, numero, rue, rue_bis, cp, ville, gps_lat, gps_lng)
|
|
VALUES (:sector_id, :address_id, :numero, :rue, :rue_bis, :cp, :ville, :gps_lat, :gps_lng)";
|
|
$stmtAddress = $this->db->prepare($queryAddress);
|
|
|
|
foreach ($addresses as $address) {
|
|
// Extraire le rue_bis si présent (généralement vide)
|
|
$rueBis = '';
|
|
|
|
$stmtAddress->execute([
|
|
'sector_id' => $sectorId,
|
|
'address_id' => $address['id'],
|
|
'numero' => $address['numero'],
|
|
'rue' => $address['voie'],
|
|
'rue_bis' => $rueBis,
|
|
'cp' => $address['code_postal'],
|
|
'ville' => $address['commune'],
|
|
'gps_lat' => $address['latitude'],
|
|
'gps_lng' => $address['longitude']
|
|
]);
|
|
}
|
|
|
|
|
|
// Créer les passages pour chaque adresse
|
|
if (!empty($users)) {
|
|
$firstUserId = $users[0]; // Premier user pour l'affectation des passages
|
|
$passageQuery = "INSERT INTO ope_pass (
|
|
fk_operation, fk_sector, fk_user, fk_adresse,
|
|
numero, rue, rue_bis, ville,
|
|
gps_lat, gps_lng, fk_type, encrypted_name,
|
|
created_at, fk_user_creat, chk_active
|
|
) VALUES (
|
|
:operation_id, :sector_id, :user_id, :fk_adresse,
|
|
:numero, :rue, :rue_bis, :ville,
|
|
:gps_lat, :gps_lng, 2, '',
|
|
NOW(), :user_creat, 1
|
|
)";
|
|
$passageStmt = $this->db->prepare($passageQuery);
|
|
|
|
$passagesCreated = 0;
|
|
foreach ($addresses as $address) {
|
|
// Vérifier si cette adresse n'est pas déjà utilisée par un passage orphelin
|
|
if (in_array($address['id'], $addressesToExclude)) {
|
|
continue; // Passer à l'adresse suivante
|
|
}
|
|
|
|
try {
|
|
// Extraire le rue_bis si présent (généralement vide)
|
|
$rueBis = '';
|
|
|
|
$passageStmt->execute([
|
|
'operation_id' => $operationId,
|
|
'sector_id' => $sectorId,
|
|
'user_id' => $firstUserId,
|
|
'fk_adresse' => $address['id'],
|
|
'numero' => $address['numero'],
|
|
'rue' => $address['voie'],
|
|
'rue_bis' => $rueBis,
|
|
'ville' => $address['commune'],
|
|
'gps_lat' => $address['latitude'],
|
|
'gps_lng' => $address['longitude'],
|
|
'user_creat' => $userId
|
|
]);
|
|
$passagesCreated++;
|
|
} catch (\Exception $e) {
|
|
$this->logService->warning('Erreur lors de la création d\'un passage', [
|
|
'address_id' => $address['id'],
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
} catch (\Exception $e) {
|
|
// En cas d'erreur avec les adresses, on ne bloque pas la création du secteur
|
|
$this->logService->error('Erreur lors du stockage des adresses du secteur', [
|
|
'sector_id' => $sectorId,
|
|
'error' => $e->getMessage(),
|
|
'entity_id' => $entityId
|
|
]);
|
|
}
|
|
|
|
$this->db->commit();
|
|
|
|
// Préparer les données de réponse
|
|
$responseData = [
|
|
'sector_id' => $sectorId
|
|
];
|
|
|
|
// Récupérer tous les passages du secteur (créés + intégrés)
|
|
$totalPassages = (isset($passagesCreated) ? $passagesCreated : 0) + $passagesIntegrated;
|
|
if ($totalPassages > 0) {
|
|
$passagesQuery = "SELECT id, fk_operation, fk_sector, fk_user, fk_type, fk_adresse, passed_at,
|
|
numero, rue, rue_bis, ville, residence, fk_habitat, appt, niveau,
|
|
gps_lat, gps_lng, nom_recu, encrypted_name, remarque, encrypted_email,
|
|
encrypted_phone, montant, fk_type_reglement, email_erreur, nb_passages
|
|
FROM ope_pass
|
|
WHERE fk_sector = :sector_id
|
|
ORDER BY id";
|
|
$passagesStmt = $this->db->prepare($passagesQuery);
|
|
$passagesStmt->execute(['sector_id' => $sectorId]);
|
|
$passages = $passagesStmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
|
|
// Déchiffrer les données sensibles des passages
|
|
$passagesDecrypted = [];
|
|
foreach ($passages as $passage) {
|
|
// Déchiffrement du nom
|
|
$passage['name'] = '';
|
|
if (!empty($passage['encrypted_name'])) {
|
|
$passage['name'] = ApiService::decryptData($passage['encrypted_name']);
|
|
}
|
|
unset($passage['encrypted_name']);
|
|
|
|
// Déchiffrement de l'email
|
|
$passage['email'] = '';
|
|
if (!empty($passage['encrypted_email'])) {
|
|
$decryptedEmail = ApiService::decryptSearchableData($passage['encrypted_email']);
|
|
if ($decryptedEmail) {
|
|
$passage['email'] = $decryptedEmail;
|
|
}
|
|
}
|
|
unset($passage['encrypted_email']);
|
|
|
|
// Déchiffrement du téléphone
|
|
$passage['phone'] = '';
|
|
if (!empty($passage['encrypted_phone'])) {
|
|
$passage['phone'] = ApiService::decryptData($passage['encrypted_phone']);
|
|
}
|
|
unset($passage['encrypted_phone']);
|
|
|
|
$passagesDecrypted[] = $passage;
|
|
}
|
|
|
|
$responseData['passages_sector'] = $passagesDecrypted;
|
|
$responseData['passages_integrated'] = $passagesIntegrated;
|
|
$responseData['passages_created'] = isset($passagesCreated) ? $passagesCreated : 0;
|
|
} else {
|
|
$responseData['passages_sector'] = [];
|
|
$responseData['passages_integrated'] = 0;
|
|
$responseData['passages_created'] = 0;
|
|
}
|
|
|
|
// Récupérer les users affectés
|
|
$usersQuery = "SELECT u.id, u.first_name, u.sect_name, u.encrypted_name, ous.fk_sector
|
|
FROM ope_users_sectors ous
|
|
JOIN users u ON ous.fk_user = u.id
|
|
WHERE ous.fk_sector = :sector_id";
|
|
$usersStmt = $this->db->prepare($usersQuery);
|
|
$usersStmt->execute(['sector_id' => $sectorId]);
|
|
$usersSectors = $usersStmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
|
|
// Déchiffrer les noms des utilisateurs
|
|
$responseData['users_sectors'] = [];
|
|
foreach ($usersSectors as $userSector) {
|
|
$userData = [
|
|
'id' => $userSector['id'],
|
|
'first_name' => $userSector['first_name'] ?? '',
|
|
'sect_name' => $userSector['sect_name'] ?? '',
|
|
'fk_sector' => $userSector['fk_sector'],
|
|
'name' => ''
|
|
];
|
|
|
|
// Déchiffrer le nom
|
|
if (!empty($userSector['encrypted_name'])) {
|
|
$userData['name'] = ApiService::decryptData($userSector['encrypted_name']);
|
|
}
|
|
|
|
$responseData['users_sectors'][] = $userData;
|
|
}
|
|
|
|
$this->logService->info('Secteur créé', [
|
|
'sector_id' => $sectorId,
|
|
'libelle' => $sectorData['libelle'],
|
|
'entity_id' => $entityId,
|
|
'user_id' => $userId,
|
|
'passages_created' => isset($passagesCreated) ? $passagesCreated : 0,
|
|
'passages_integrated' => $passagesIntegrated,
|
|
'users_assigned' => count($users)
|
|
]);
|
|
|
|
// Construire la réponse selon la norme de l'API (sans groupe "data")
|
|
$response = [
|
|
'status' => 'success',
|
|
'message' => 'Secteur créé avec succès',
|
|
'sector' => [
|
|
'id' => $sectorId,
|
|
'libelle' => $sectorData['libelle'],
|
|
'color' => $sectorData['color'],
|
|
'sector' => $sectorData['sector']
|
|
]
|
|
];
|
|
|
|
// Ajouter les autres données directement à la racine
|
|
if (isset($responseData['passages_sector'])) {
|
|
$response['passages_sector'] = $responseData['passages_sector'];
|
|
}
|
|
if (isset($responseData['passages_integrated'])) {
|
|
$response['passages_integrated'] = $responseData['passages_integrated'];
|
|
}
|
|
if (isset($responseData['passages_created'])) {
|
|
$response['passages_created'] = $responseData['passages_created'];
|
|
}
|
|
if (isset($responseData['users_sectors'])) {
|
|
$response['users_sectors'] = $responseData['users_sectors'];
|
|
}
|
|
|
|
Response::json($response, 201);
|
|
|
|
} catch (\Exception $e) {
|
|
if ($this->db->inTransaction()) {
|
|
$this->db->rollBack();
|
|
}
|
|
$this->logService->error('Erreur lors de la création du secteur', [
|
|
'error' => $e->getMessage(),
|
|
'data' => $data ?? null
|
|
]);
|
|
Response::json(['status' => 'error', 'message' => 'Erreur lors de la création du secteur'], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /sectors/{id} - Modifier un secteur
|
|
*/
|
|
public function update($id): void
|
|
{
|
|
try {
|
|
$data = json_decode(file_get_contents('php://input'), true);
|
|
$entityId = $_SESSION['entity_id'] ?? null;
|
|
|
|
if (!$entityId) {
|
|
Response::json(['status' => 'error', 'message' => 'Entité non définie'], 400);
|
|
return;
|
|
}
|
|
|
|
// Vérifier que le secteur appartient à l'entité
|
|
$checkQuery = "SELECT s.id
|
|
FROM ope_sectors s
|
|
JOIN operations o ON s.fk_operation = o.id
|
|
WHERE s.id = :id AND o.fk_entite = :entity_id";
|
|
$checkStmt = $this->db->prepare($checkQuery);
|
|
$checkStmt->execute(['id' => $id, 'entity_id' => $entityId]);
|
|
|
|
if (!$checkStmt->fetch()) {
|
|
Response::json(['status' => 'error', 'message' => 'Secteur non trouvé'], 404);
|
|
return;
|
|
}
|
|
|
|
$this->db->beginTransaction();
|
|
|
|
// Mise à jour du secteur
|
|
$updateFields = [];
|
|
$params = ['id' => $id];
|
|
|
|
if (isset($data['libelle'])) {
|
|
$updateFields[] = 'libelle = :libelle';
|
|
$params['libelle'] = $data['libelle'];
|
|
}
|
|
|
|
if (isset($data['color'])) {
|
|
$updateFields[] = 'color = :color';
|
|
$params['color'] = $data['color'];
|
|
}
|
|
|
|
if (isset($data['sector'])) {
|
|
$updateFields[] = 'sector = :sector';
|
|
$params['sector'] = $data['sector'];
|
|
}
|
|
|
|
if (!empty($updateFields)) {
|
|
$query = "UPDATE ope_sectors SET " . implode(', ', $updateFields) . " WHERE id = :id";
|
|
$stmt = $this->db->prepare($query);
|
|
$stmt->execute($params);
|
|
}
|
|
|
|
// Gestion des membres
|
|
if (isset($data['membres'])) {
|
|
// Supprimer les affectations existantes
|
|
$deleteQuery = "DELETE FROM ope_users_sectors WHERE fk_sector = :sector_id";
|
|
$deleteStmt = $this->db->prepare($deleteQuery);
|
|
$deleteStmt->execute(['sector_id' => $id]);
|
|
|
|
// Ajouter les nouvelles affectations
|
|
if (!empty($data['membres'])) {
|
|
$insertQuery = "INSERT INTO ope_users_sectors (fk_user, fk_sector) VALUES (:user_id, :sector_id)";
|
|
$insertStmt = $this->db->prepare($insertQuery);
|
|
|
|
foreach ($data['membres'] as $memberId) {
|
|
$insertStmt->execute([
|
|
'user_id' => $memberId,
|
|
'sector_id' => $id
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Gérer les passages si le secteur a changé
|
|
$passageCounters = [
|
|
'passages_orphaned' => 0,
|
|
'passages_updated' => 0,
|
|
'passages_created' => 0,
|
|
'passages_kept' => 0
|
|
];
|
|
if (isset($data['sector'])) {
|
|
// Mettre à jour les adresses du secteur AVANT de traiter les passages
|
|
try {
|
|
// Supprimer les anciennes adresses
|
|
$deleteAddressQuery = "DELETE FROM sectors_adresses WHERE fk_sector = :sector_id";
|
|
$deleteAddressStmt = $this->db->prepare($deleteAddressQuery);
|
|
$deleteAddressStmt->execute(['sector_id' => $id]);
|
|
|
|
// Traiter les nouvelles coordonnées
|
|
$points = explode('#', rtrim($data['sector'], '#'));
|
|
$coordinates = [];
|
|
|
|
foreach ($points as $point) {
|
|
if (!empty($point)) {
|
|
list($lat, $lng) = explode('/', $point);
|
|
$coordinates[] = [floatval($lat), floatval($lng)];
|
|
}
|
|
}
|
|
|
|
// Récupérer et stocker les nouvelles adresses
|
|
$this->logService->info('[UPDATE] Récupération des adresses', [
|
|
'sector_id' => $id,
|
|
'entity_id' => $entityId,
|
|
'nb_points' => count($coordinates)
|
|
]);
|
|
|
|
$addresses = $this->addressService->getAddressesInPolygon($coordinates, $entityId);
|
|
|
|
$this->logService->info('[UPDATE] Adresses récupérées', [
|
|
'sector_id' => $id,
|
|
'nb_addresses' => count($addresses)
|
|
]);
|
|
|
|
if (!empty($addresses)) {
|
|
$queryAddress = "INSERT INTO sectors_adresses (fk_sector, fk_adresse, numero, rue, cp, ville, gps_lat, gps_lng)
|
|
VALUES (:sector_id, :address_id, :numero, :rue, :cp, :ville, :gps_lat, :gps_lng)";
|
|
$stmtAddress = $this->db->prepare($queryAddress);
|
|
|
|
foreach ($addresses as $address) {
|
|
$stmtAddress->execute([
|
|
'sector_id' => $id,
|
|
'address_id' => $address['id'],
|
|
'numero' => $address['numero'],
|
|
'rue' => $address['voie'],
|
|
'cp' => $address['code_postal'],
|
|
'ville' => $address['commune'],
|
|
'gps_lat' => $address['latitude'],
|
|
'gps_lng' => $address['longitude']
|
|
]);
|
|
}
|
|
|
|
$this->logService->info('[UPDATE] Adresses stockées dans sectors_adresses', [
|
|
'sector_id' => $id,
|
|
'nb_stored' => count($addresses)
|
|
]);
|
|
} else {
|
|
$this->logService->warning('[UPDATE] Aucune adresse trouvée pour le secteur', [
|
|
'sector_id' => $id,
|
|
'entity_id' => $entityId
|
|
]);
|
|
}
|
|
|
|
// Vérifier si c'est un problème de connexion à la base d'adresses
|
|
if (!$this->addressService->isConnected()) {
|
|
$this->logService->warning('[UPDATE] Base d\'adresses non accessible - passages créés sans adresses', [
|
|
'sector_id' => $id
|
|
]);
|
|
}
|
|
} catch (\Exception $e) {
|
|
$this->logService->error('[UPDATE] Erreur lors de la mise à jour des adresses du secteur', [
|
|
'sector_id' => $id,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
|
|
// Maintenant que les adresses sont mises à jour, traiter les passages
|
|
$this->logService->info('[UPDATE] Début mise à jour des passages', ['sector_id' => $id]);
|
|
$passageCounters = $this->updatePassagesForSector($id, $data['sector']);
|
|
}
|
|
|
|
$this->db->commit();
|
|
|
|
// Récupérer le secteur mis à jour
|
|
$query = "
|
|
SELECT
|
|
s.id,
|
|
s.libelle,
|
|
s.color,
|
|
s.sector
|
|
FROM ope_sectors s
|
|
WHERE s.id = :id
|
|
";
|
|
|
|
$stmt = $this->db->prepare($query);
|
|
$stmt->execute(['id' => $id]);
|
|
$sector = $stmt->fetch(\PDO::FETCH_ASSOC);
|
|
|
|
// Récupérer tous les passages du secteur
|
|
$passagesQuery = "SELECT id, fk_operation, fk_sector, fk_user, fk_type, fk_adresse, passed_at,
|
|
numero, rue, rue_bis, ville, residence, fk_habitat, appt, niveau,
|
|
gps_lat, gps_lng, nom_recu, encrypted_name, remarque, encrypted_email,
|
|
encrypted_phone, montant, fk_type_reglement, email_erreur, nb_passages
|
|
FROM ope_pass
|
|
WHERE fk_sector = :sector_id
|
|
ORDER BY id";
|
|
$passagesStmt = $this->db->prepare($passagesQuery);
|
|
$passagesStmt->execute(['sector_id' => $id]);
|
|
$passages = $passagesStmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
|
|
// Déchiffrer les données sensibles des passages
|
|
$passagesDecrypted = [];
|
|
foreach ($passages as $passage) {
|
|
// Déchiffrement du nom
|
|
$passage['name'] = '';
|
|
if (!empty($passage['encrypted_name'])) {
|
|
$passage['name'] = ApiService::decryptData($passage['encrypted_name']);
|
|
}
|
|
unset($passage['encrypted_name']);
|
|
|
|
// Déchiffrement de l'email
|
|
$passage['email'] = '';
|
|
if (!empty($passage['encrypted_email'])) {
|
|
$decryptedEmail = ApiService::decryptSearchableData($passage['encrypted_email']);
|
|
if ($decryptedEmail) {
|
|
$passage['email'] = $decryptedEmail;
|
|
}
|
|
}
|
|
unset($passage['encrypted_email']);
|
|
|
|
// Déchiffrement du téléphone
|
|
$passage['phone'] = '';
|
|
if (!empty($passage['encrypted_phone'])) {
|
|
$passage['phone'] = ApiService::decryptData($passage['encrypted_phone']);
|
|
}
|
|
unset($passage['encrypted_phone']);
|
|
|
|
$passagesDecrypted[] = $passage;
|
|
}
|
|
|
|
// Récupérer les users affectés
|
|
$usersQuery = "SELECT u.id, u.first_name, u.sect_name, u.encrypted_name, ous.fk_sector
|
|
FROM ope_users_sectors ous
|
|
JOIN users u ON ous.fk_user = u.id
|
|
WHERE ous.fk_sector = :sector_id";
|
|
$usersStmt = $this->db->prepare($usersQuery);
|
|
$usersStmt->execute(['sector_id' => $id]);
|
|
$usersSectors = $usersStmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
|
|
// Déchiffrer les noms des utilisateurs
|
|
$usersDecrypted = [];
|
|
foreach ($usersSectors as $userSector) {
|
|
$userData = [
|
|
'id' => $userSector['id'],
|
|
'first_name' => $userSector['first_name'] ?? '',
|
|
'sect_name' => $userSector['sect_name'] ?? '',
|
|
'fk_sector' => $userSector['fk_sector'],
|
|
'name' => ''
|
|
];
|
|
|
|
// Déchiffrer le nom
|
|
if (!empty($userSector['encrypted_name'])) {
|
|
$userData['name'] = ApiService::decryptData($userSector['encrypted_name']);
|
|
}
|
|
|
|
$usersDecrypted[] = $userData;
|
|
}
|
|
|
|
$this->logService->info('Secteur modifié', [
|
|
'sector_id' => $id,
|
|
'updates' => array_keys($data),
|
|
'passage_counters' => $passageCounters,
|
|
'user_id' => $_SESSION['user_id'] ?? null
|
|
]);
|
|
|
|
// Construire la réponse identique à create avec les compteurs supplémentaires
|
|
$response = [
|
|
'status' => 'success',
|
|
'message' => 'Secteur modifié avec succès',
|
|
'sector' => $sector,
|
|
'passages_sector' => $passagesDecrypted,
|
|
'passages_orphaned' => $passageCounters['passages_orphaned'],
|
|
'passages_deleted' => $passageCounters['passages_deleted'],
|
|
'passages_updated' => $passageCounters['passages_updated'],
|
|
'passages_created' => $passageCounters['passages_created'],
|
|
'passages_total' => count($passagesDecrypted),
|
|
'users_sectors' => $usersDecrypted
|
|
];
|
|
|
|
Response::json($response);
|
|
|
|
} catch (\Exception $e) {
|
|
// Vérifier si une transaction est active avant de faire un rollback
|
|
if ($this->db->inTransaction()) {
|
|
$this->db->rollBack();
|
|
}
|
|
$this->logService->error('Erreur lors de la modification du secteur', [
|
|
'sector_id' => $id,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json(['status' => 'error', 'message' => 'Erreur lors de la modification du secteur'], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /sectors/{id}/addresses - Récupérer les adresses d'un secteur
|
|
*/
|
|
public function getAddresses($id): void
|
|
{
|
|
try {
|
|
$entityId = $_SESSION['entity_id'] ?? null;
|
|
|
|
if (!$entityId) {
|
|
Response::json(['status' => 'error', 'message' => 'Entité non définie'], 400);
|
|
return;
|
|
}
|
|
|
|
// Vérifier que le secteur appartient à l'entité
|
|
$checkQuery = "SELECT s.id
|
|
FROM ope_sectors s
|
|
JOIN operations o ON s.fk_operation = o.id
|
|
WHERE s.id = :id AND o.fk_entite = :entity_id";
|
|
$checkStmt = $this->db->prepare($checkQuery);
|
|
$checkStmt->execute(['id' => $id, 'entity_id' => $entityId]);
|
|
|
|
if (!$checkStmt->fetch()) {
|
|
Response::json(['status' => 'error', 'message' => 'Secteur non trouvé'], 404);
|
|
return;
|
|
}
|
|
|
|
// Récupérer les adresses du secteur
|
|
$query = "
|
|
SELECT
|
|
fk_address as id,
|
|
numero,
|
|
voie,
|
|
code_postal,
|
|
commune,
|
|
latitude,
|
|
longitude
|
|
FROM sectors_adresses
|
|
WHERE fk_sector = :sector_id
|
|
ORDER BY commune, voie, CAST(numero AS UNSIGNED)
|
|
";
|
|
|
|
$stmt = $this->db->prepare($query);
|
|
$stmt->execute(['sector_id' => $id]);
|
|
$addresses = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
|
|
// Compter le total
|
|
$countQuery = "SELECT COUNT(*) as total FROM sectors_adresses WHERE fk_sector = :sector_id";
|
|
$countStmt = $this->db->prepare($countQuery);
|
|
$countStmt->execute(['sector_id' => $id]);
|
|
$total = $countStmt->fetch()['total'];
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'data' => $addresses,
|
|
'total' => $total
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logService->error('Erreur lors de la récupération des adresses du secteur', [
|
|
'sector_id' => $id,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json(['status' => 'error', 'message' => 'Erreur lors de la récupération des adresses'], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* DELETE /sectors/{id} - Supprimer un secteur
|
|
*/
|
|
public function delete($id): void
|
|
{
|
|
try {
|
|
// Récupérer les données de la requête si présentes (pour les API stateless)
|
|
$data = json_decode(file_get_contents('php://input'), true) ?? [];
|
|
$entityId = $data['fk_entite'] ?? $_SESSION['entity_id'] ?? null;
|
|
|
|
if (!$entityId) {
|
|
Response::json(['status' => 'error', 'message' => 'Entité non définie'], 400);
|
|
return;
|
|
}
|
|
|
|
// Vérifier que le secteur existe et récupérer ses informations
|
|
$checkQuery = "SELECT s.id, s.libelle, o.fk_entite
|
|
FROM ope_sectors s
|
|
JOIN operations o ON s.fk_operation = o.id
|
|
WHERE s.id = :id";
|
|
$checkStmt = $this->db->prepare($checkQuery);
|
|
$checkStmt->execute(['id' => $id]);
|
|
$sector = $checkStmt->fetch();
|
|
|
|
if (!$sector || $sector['fk_entite'] != $entityId) {
|
|
Response::json(['status' => 'error', 'message' => 'Secteur non trouvé ou non autorisé'], 404);
|
|
return;
|
|
}
|
|
|
|
$this->db->beginTransaction();
|
|
|
|
// Compter les passages à supprimer (fk_type=2 et encrypted_name vide)
|
|
$countDeleteQuery = "SELECT COUNT(*) as count
|
|
FROM ope_pass
|
|
WHERE fk_sector = :sector_id
|
|
AND fk_type = 2
|
|
AND (encrypted_name IS NULL OR encrypted_name = '')";
|
|
$countDeleteStmt = $this->db->prepare($countDeleteQuery);
|
|
$countDeleteStmt->execute(['sector_id' => $id]);
|
|
$passagesToDelete = $countDeleteStmt->fetch()['count'];
|
|
|
|
// Récupérer les passages à réaffecter avant de les modifier
|
|
$getPassagesToUpdateQuery = "SELECT id, fk_operation, fk_sector, fk_user, fk_type, fk_adresse, passed_at,
|
|
numero, rue, rue_bis, ville, residence, fk_habitat, appt, niveau,
|
|
gps_lat, gps_lng, nom_recu, encrypted_name, remarque, encrypted_email,
|
|
encrypted_phone, montant, fk_type_reglement, email_erreur, nb_passages
|
|
FROM ope_pass
|
|
WHERE fk_sector = :sector_id
|
|
AND NOT (fk_type = 2 AND (encrypted_name IS NULL OR encrypted_name = ''))";
|
|
$getPassagesToUpdateStmt = $this->db->prepare($getPassagesToUpdateQuery);
|
|
$getPassagesToUpdateStmt->execute(['sector_id' => $id]);
|
|
$passagesToUpdate = $getPassagesToUpdateStmt->fetchAll(\PDO::FETCH_ASSOC);
|
|
|
|
// Supprimer les passages avec fk_type=2 et encrypted_name vide
|
|
$deletePassagesQuery = "DELETE FROM ope_pass
|
|
WHERE fk_sector = :sector_id
|
|
AND fk_type = 2
|
|
AND (encrypted_name IS NULL OR encrypted_name = '')";
|
|
$deletePassagesStmt = $this->db->prepare($deletePassagesQuery);
|
|
$deletePassagesStmt->execute(['sector_id' => $id]);
|
|
|
|
// Réaffecter les autres passages au secteur NULL
|
|
$updatePassagesQuery = "UPDATE ope_pass
|
|
SET fk_sector = NULL
|
|
WHERE fk_sector = :sector_id
|
|
AND NOT (fk_type = 2 AND (encrypted_name IS NULL OR encrypted_name = ''))";
|
|
$updatePassagesStmt = $this->db->prepare($updatePassagesQuery);
|
|
$updatePassagesStmt->execute(['sector_id' => $id]);
|
|
|
|
// Supprimer les affectations de membres
|
|
$deleteMembersQuery = "DELETE FROM ope_users_sectors WHERE fk_sector = :sector_id";
|
|
$deleteMembersStmt = $this->db->prepare($deleteMembersQuery);
|
|
$deleteMembersStmt->execute(['sector_id' => $id]);
|
|
|
|
// Supprimer les adresses associées au secteur
|
|
$deleteAddressesQuery = "DELETE FROM sectors_adresses WHERE fk_sector = :sector_id";
|
|
$deleteAddressesStmt = $this->db->prepare($deleteAddressesQuery);
|
|
$deleteAddressesStmt->execute(['sector_id' => $id]);
|
|
|
|
// Supprimer le secteur
|
|
$deleteQuery = "DELETE FROM ope_sectors WHERE id = :id";
|
|
$deleteStmt = $this->db->prepare($deleteQuery);
|
|
$deleteStmt->execute(['id' => $id]);
|
|
|
|
$this->db->commit();
|
|
|
|
// Déchiffrer les données sensibles des passages
|
|
$passagesDecrypted = [];
|
|
foreach ($passagesToUpdate as $passage) {
|
|
// Déchiffrement du nom
|
|
$passage['name'] = '';
|
|
if (!empty($passage['encrypted_name'])) {
|
|
$passage['name'] = ApiService::decryptData($passage['encrypted_name']);
|
|
}
|
|
unset($passage['encrypted_name']);
|
|
|
|
// Déchiffrement de l'email
|
|
$passage['email'] = '';
|
|
if (!empty($passage['encrypted_email'])) {
|
|
$decryptedEmail = ApiService::decryptSearchableData($passage['encrypted_email']);
|
|
if ($decryptedEmail) {
|
|
$passage['email'] = $decryptedEmail;
|
|
}
|
|
}
|
|
unset($passage['encrypted_email']);
|
|
|
|
// Déchiffrement du téléphone
|
|
$passage['phone'] = '';
|
|
if (!empty($passage['encrypted_phone'])) {
|
|
$passage['phone'] = ApiService::decryptData($passage['encrypted_phone']);
|
|
}
|
|
unset($passage['encrypted_phone']);
|
|
|
|
$passagesDecrypted[] = $passage;
|
|
}
|
|
|
|
$this->logService->info('Secteur supprimé', [
|
|
'sector_id' => $id,
|
|
'libelle' => $sector['libelle'],
|
|
'passages_deleted' => $passagesToDelete,
|
|
'passages_reassigned' => count($passagesToUpdate),
|
|
'user_id' => $_SESSION['user_id'] ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'message' => 'Secteur supprimé avec succès',
|
|
'passages_deleted' => $passagesToDelete,
|
|
'passages_reassigned' => count($passagesToUpdate),
|
|
'passages_sector' => $passagesDecrypted
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
$this->db->rollBack();
|
|
$this->logService->error('Erreur lors de la suppression du secteur', [
|
|
'sector_id' => $id,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json(['status' => 'error', 'message' => 'Erreur lors de la suppression du secteur'], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /sectors/department-boundaries/status - Vérifier le statut des contours départementaux
|
|
*/
|
|
public function departmentBoundariesStatus(): void
|
|
{
|
|
try {
|
|
$status = $this->boundaryService->checkDepartmentContoursStatus();
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'data' => $status
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logService->error('Erreur lors de la vérification des contours départementaux', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json(['status' => 'error', 'message' => 'Erreur lors de la vérification'], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /sectors/check-boundaries - Vérifier si un secteur respecte les limites départementales
|
|
*/
|
|
public function checkBoundaries(): void
|
|
{
|
|
try {
|
|
$data = json_decode(file_get_contents('php://input'), true);
|
|
|
|
if (!isset($data['sector']) || !isset($data['entity_id'])) {
|
|
Response::json(['status' => 'error', 'message' => 'Données manquantes'], 400);
|
|
return;
|
|
}
|
|
|
|
// Traiter les coordonnées
|
|
$sector = $data['sector'];
|
|
$points = explode('#', rtrim($sector, '#'));
|
|
$coordinates = [];
|
|
|
|
foreach ($points as $point) {
|
|
if (!empty($point)) {
|
|
list($lat, $lng) = explode('/', $point);
|
|
$coordinates[] = [floatval($lat), floatval($lng)];
|
|
}
|
|
}
|
|
|
|
// Récupérer le département de l'entité
|
|
$deptQuery = "SELECT departement, nom FROM entites WHERE id = :entity_id";
|
|
$deptStmt = $this->db->prepare($deptQuery);
|
|
$deptStmt->execute(['entity_id' => $data['entity_id']]);
|
|
$entity = $deptStmt->fetch();
|
|
|
|
if (!$entity || !$entity['departement']) {
|
|
Response::json(['status' => 'error', 'message' => 'Département de l\'entité non défini'], 400);
|
|
return;
|
|
}
|
|
|
|
// Vérifier les limites
|
|
$boundaryCheck = $this->boundaryService->checkSectorInDepartment($coordinates, $entity['departement']);
|
|
|
|
// Récupérer aussi la liste de tous les départements touchés
|
|
$intersectingDepts = $this->boundaryService->getDepartmentsForSector($coordinates);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'data' => [
|
|
'is_valid' => $boundaryCheck['is_contained'],
|
|
'entity_department' => $entity['departement'],
|
|
'message' => $boundaryCheck['message'],
|
|
'departments' => $intersectingDepts
|
|
]
|
|
]);
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logService->error('Erreur lors de la vérification des limites', [
|
|
'error' => $e->getMessage()
|
|
]);
|
|
Response::json(['status' => 'error', 'message' => 'Erreur lors de la vérification'], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mettre à jour les passages affectés à un secteur lors de la modification du périmètre
|
|
* Retourne un tableau avec les compteurs détaillés
|
|
*/
|
|
private function updatePassagesForSector($sectorId, $newSectorCoords): array
|
|
{
|
|
$counters = [
|
|
'passages_orphaned' => 0,
|
|
'passages_deleted' => 0,
|
|
'passages_updated' => 0,
|
|
'passages_created' => 0,
|
|
'passages_kept' => 0
|
|
];
|
|
|
|
try {
|
|
// Récupérer l'opération et l'entité du secteur
|
|
$sectorQuery = "SELECT o.id as operation_id, o.fk_entite, s.fk_operation
|
|
FROM ope_sectors s
|
|
JOIN operations o ON s.fk_operation = o.id
|
|
WHERE s.id = :sector_id";
|
|
$sectorStmt = $this->db->prepare($sectorQuery);
|
|
$sectorStmt->execute(['sector_id' => $sectorId]);
|
|
$sectorInfo = $sectorStmt->fetch();
|
|
|
|
if (!$sectorInfo) {
|
|
return 0;
|
|
}
|
|
|
|
$operationId = $sectorInfo['operation_id'];
|
|
$entityId = $sectorInfo['fk_entite'];
|
|
|
|
// Traiter les coordonnées pour créer le polygone
|
|
$points = explode('#', rtrim($newSectorCoords, '#'));
|
|
$coordinates = [];
|
|
$polygonPoints = [];
|
|
|
|
foreach ($points as $point) {
|
|
if (!empty($point)) {
|
|
list($lat, $lng) = explode('/', $point);
|
|
$coordinates[] = [floatval($lat), floatval($lng)];
|
|
$polygonPoints[] = floatval($lng) . ' ' . floatval($lat); // longitude latitude pour SQL
|
|
}
|
|
}
|
|
$polygonPoints[] = $polygonPoints[0]; // Fermer le polygone
|
|
$polygonString = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
|
|
|
// 1. VÉRIFICATION GÉOGRAPHIQUE DES PASSAGES EXISTANTS
|
|
$checkPassagesQuery = "SELECT id, gps_lat, gps_lng, fk_type, encrypted_name
|
|
FROM ope_pass
|
|
WHERE fk_sector = :sector_id
|
|
AND gps_lat IS NOT NULL
|
|
AND gps_lng IS NOT NULL";
|
|
$checkStmt = $this->db->prepare($checkPassagesQuery);
|
|
$checkStmt->execute(['sector_id' => $sectorId]);
|
|
$existingPassages = $checkStmt->fetchAll();
|
|
|
|
$passagesToDelete = [];
|
|
|
|
foreach ($existingPassages as $passage) {
|
|
// Vérifier si le passage est dans le nouveau polygone
|
|
$pointInPolygonQuery = "SELECT ST_Contains(ST_GeomFromText(:polygon, 4326),
|
|
POINT(CAST(:lng AS DECIMAL(10,8)),
|
|
CAST(:lat AS DECIMAL(10,8)))) as is_inside";
|
|
$pointStmt = $this->db->prepare($pointInPolygonQuery);
|
|
$pointStmt->execute([
|
|
'polygon' => $polygonString,
|
|
'lng' => $passage['gps_lng'],
|
|
'lat' => $passage['gps_lat']
|
|
]);
|
|
$result = $pointStmt->fetch();
|
|
|
|
if ($result['is_inside'] == 0) {
|
|
// Le passage est hors du nouveau périmètre
|
|
// Vérifier si c'est un passage non visité (fk_type=2 ET encrypted_name vide)
|
|
if ($passage['fk_type'] == 2 && ($passage['encrypted_name'] === '' || $passage['encrypted_name'] === null)) {
|
|
// Passage non visité : à supprimer
|
|
$passagesToDelete[] = $passage['id'];
|
|
$counters['passages_deleted'] = ($counters['passages_deleted'] ?? 0) + 1;
|
|
} else {
|
|
// Passage visité : mettre en orphelin
|
|
$orphanQuery = "UPDATE ope_pass SET fk_sector = NULL WHERE id = :passage_id";
|
|
$orphanStmt = $this->db->prepare($orphanQuery);
|
|
$orphanStmt->execute(['passage_id' => $passage['id']]);
|
|
$counters['passages_orphaned']++;
|
|
}
|
|
} else {
|
|
$counters['passages_kept']++;
|
|
}
|
|
}
|
|
|
|
// Supprimer les passages non visités qui sont hors zone
|
|
if (!empty($passagesToDelete)) {
|
|
$deleteQuery = "DELETE FROM ope_pass WHERE id IN (" . implode(',', $passagesToDelete) . ")";
|
|
$this->db->exec($deleteQuery);
|
|
}
|
|
|
|
// 2. CRÉATION/MISE À JOUR DES PASSAGES POUR LES NOUVELLES ADRESSES
|
|
// Récupérer toutes les adresses du secteur depuis sectors_adresses
|
|
$addressesQuery = "SELECT * FROM sectors_adresses WHERE fk_sector = :sector_id";
|
|
$addressesStmt = $this->db->prepare($addressesQuery);
|
|
$addressesStmt->execute(['sector_id' => $sectorId]);
|
|
$addresses = $addressesStmt->fetchAll();
|
|
|
|
$this->logService->info('[updatePassagesForSector] Adresses dans sectors_adresses', [
|
|
'sector_id' => $sectorId,
|
|
'nb_addresses' => count($addresses)
|
|
]);
|
|
|
|
// Récupérer le premier utilisateur affecté au secteur
|
|
$userQuery = "SELECT fk_user FROM ope_users_sectors WHERE fk_sector = :sector_id LIMIT 1";
|
|
$userStmt = $this->db->prepare($userQuery);
|
|
$userStmt->execute(['sector_id' => $sectorId]);
|
|
$firstUser = $userStmt->fetch();
|
|
$firstUserId = $firstUser ? $firstUser['fk_user'] : null;
|
|
|
|
if ($firstUserId && !empty($addresses)) {
|
|
$this->logService->info('[updatePassagesForSector] Création passages pour user', [
|
|
'user_id' => $firstUserId,
|
|
'nb_addresses_to_process' => count($addresses)
|
|
]);
|
|
// Préparer la requête de création de passage (même format que dans create)
|
|
$createPassageQuery = "INSERT INTO ope_pass (
|
|
fk_operation, fk_sector, fk_user, fk_adresse,
|
|
numero, rue, rue_bis, ville,
|
|
gps_lat, gps_lng, fk_type, encrypted_name,
|
|
created_at, fk_user_creat, chk_active
|
|
) VALUES (
|
|
:operation_id, :sector_id, :user_id, :fk_adresse,
|
|
:numero, :rue, :rue_bis, :ville,
|
|
:gps_lat, :gps_lng, 2, '',
|
|
NOW(), :user_creat, 1
|
|
)";
|
|
$createStmt = $this->db->prepare($createPassageQuery);
|
|
|
|
foreach ($addresses as $address) {
|
|
// 2.1 Vérification primaire par fk_adresse
|
|
if (!empty($address['fk_adresse'])) {
|
|
$checkByAddressQuery = "SELECT id FROM ope_pass
|
|
WHERE fk_operation = :operation_id
|
|
AND fk_adresse = :fk_adresse";
|
|
$checkByAddressStmt = $this->db->prepare($checkByAddressQuery);
|
|
$checkByAddressStmt->execute([
|
|
'operation_id' => $operationId,
|
|
'fk_adresse' => $address['fk_adresse']
|
|
]);
|
|
|
|
if ($checkByAddressStmt->fetch()) {
|
|
continue; // Passage déjà existant, passer au suivant
|
|
}
|
|
}
|
|
|
|
// 2.2 Vérification secondaire par données d'adresse
|
|
$checkByDataQuery = "SELECT id FROM ope_pass
|
|
WHERE fk_operation = :operation_id
|
|
AND numero = :numero
|
|
AND rue_bis = :rue_bis
|
|
AND rue = :rue
|
|
AND ville = :ville";
|
|
$checkByDataStmt = $this->db->prepare($checkByDataQuery);
|
|
$checkByDataStmt->execute([
|
|
'operation_id' => $operationId,
|
|
'numero' => $address['numero'],
|
|
'rue_bis' => $address['rue_bis'],
|
|
'rue' => $address['rue'],
|
|
'ville' => $address['ville']
|
|
]);
|
|
|
|
$matchingPassages = $checkByDataStmt->fetchAll();
|
|
|
|
if (!empty($matchingPassages)) {
|
|
// Mettre à jour les passages trouvés avec le fk_adresse
|
|
if (!empty($address['fk_adresse'])) {
|
|
$updateQuery = "UPDATE ope_pass SET fk_adresse = :fk_adresse WHERE id = :passage_id";
|
|
$updateStmt = $this->db->prepare($updateQuery);
|
|
|
|
foreach ($matchingPassages as $matchingPassage) {
|
|
$updateStmt->execute([
|
|
'fk_adresse' => $address['fk_adresse'],
|
|
'passage_id' => $matchingPassage['id']
|
|
]);
|
|
$counters['passages_updated']++;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// 2.3 Création du passage (aucun passage existant trouvé)
|
|
try {
|
|
$createStmt->execute([
|
|
'operation_id' => $operationId,
|
|
'sector_id' => $sectorId,
|
|
'user_id' => $firstUserId,
|
|
'fk_adresse' => $address['fk_adresse'],
|
|
'numero' => $address['numero'],
|
|
'rue' => $address['rue'],
|
|
'rue_bis' => $address['rue_bis'],
|
|
'ville' => $address['ville'],
|
|
'gps_lat' => $address['gps_lat'],
|
|
'gps_lng' => $address['gps_lng'],
|
|
'user_creat' => $_SESSION['user_id'] ?? null
|
|
]);
|
|
$counters['passages_created']++;
|
|
} catch (\Exception $e) {
|
|
$this->logService->warning('Erreur lors de la création d\'un passage pendant update', [
|
|
'sector_id' => $sectorId,
|
|
'address' => $address,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
} else {
|
|
$this->logService->warning('[updatePassagesForSector] Pas de création de passages', [
|
|
'reason' => !$firstUserId ? 'Pas d\'utilisateur affecté' : 'Pas d\'adresses',
|
|
'first_user_id' => $firstUserId,
|
|
'nb_addresses' => count($addresses)
|
|
]);
|
|
}
|
|
|
|
|
|
// Retourner les compteurs détaillés
|
|
$this->logService->info('[updatePassagesForSector] Fin traitement', [
|
|
'sector_id' => $sectorId,
|
|
'counters' => $counters
|
|
]);
|
|
return $counters;
|
|
|
|
} catch (\Exception $e) {
|
|
$this->logService->error('Erreur lors de la mise à jour des passages', [
|
|
'sector_id' => $sectorId,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
return [
|
|
'passages_orphaned' => 0,
|
|
'passages_deleted' => 0,
|
|
'passages_updated' => 0,
|
|
'passages_created' => 0,
|
|
'passages_kept' => 0
|
|
];
|
|
}
|
|
}
|
|
} |