feat: Gestion des secteurs et migration v3.0.4+304
- 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>
This commit is contained in:
345
api/src/Services/AddressService.php
Normal file
345
api/src/Services/AddressService.php
Normal file
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/LogService.php';
|
||||
|
||||
class AddressService {
|
||||
private ?PDO $addressesDb = null;
|
||||
private PDO $mainDb;
|
||||
private LogService $logService;
|
||||
|
||||
public function __construct() {
|
||||
$this->logService = new LogService();
|
||||
try {
|
||||
$this->addressesDb = AddressesDatabase::getInstance();
|
||||
$this->logService->info('[AddressService] Connexion à la base d\'adresses réussie');
|
||||
} catch (\Exception $e) {
|
||||
// Si la connexion échoue, on continue sans la base d'adresses
|
||||
$this->logService->error('[AddressService] Connexion à la base d\'adresses impossible', [
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
$this->addressesDb = null;
|
||||
}
|
||||
$this->mainDb = Database::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si la connexion à la base d'adresses est active
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnected(): bool {
|
||||
return $this->addressesDb !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Détermine le département de l'entité courante
|
||||
*
|
||||
* @param int|null $entityId ID de l'entité
|
||||
* @return string|null Code département (ex: "22", "23")
|
||||
*/
|
||||
private function getDepartmentForEntity(?int $entityId = null): ?string {
|
||||
if (!$entityId) {
|
||||
$entityId = $_SESSION['entity_id'] ?? null;
|
||||
}
|
||||
|
||||
if (!$entityId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
$query = "SELECT departement FROM entites WHERE id = :entity_id";
|
||||
$stmt = $this->mainDb->prepare($query);
|
||||
$stmt->execute(['entity_id' => $entityId]);
|
||||
$result = $stmt->fetch();
|
||||
|
||||
return $result ? $result['departement'] : null;
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère toutes les adresses contenues dans un polygone défini par des coordonnées
|
||||
* Gère automatiquement les secteurs multi-départements
|
||||
*
|
||||
* @param array $coordinates Array de coordonnées [[lat, lng], [lat, lng], ...]
|
||||
* @param int|null $entityId ID de l'entité (pour déterminer le département principal)
|
||||
* @return array Array des adresses trouvées
|
||||
*/
|
||||
public function getAddressesInPolygon(array $coordinates, ?int $entityId = null): array {
|
||||
// Si pas de connexion à la base d'adresses, retourner un tableau vide
|
||||
if (!$this->addressesDb) {
|
||||
$this->logService->error('[AddressService] Pas de connexion à la base d\'adresses externe', [
|
||||
'entity_id' => $entityId
|
||||
]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$this->logService->info('[AddressService] Début recherche adresses', [
|
||||
'entity_id' => $entityId,
|
||||
'nb_coordinates' => count($coordinates)
|
||||
]);
|
||||
|
||||
if (count($coordinates) < 3) {
|
||||
throw new InvalidArgumentException("Un polygone doit avoir au moins 3 points");
|
||||
}
|
||||
|
||||
// D'abord, déterminer tous les départements touchés par ce secteur
|
||||
require_once __DIR__ . '/DepartmentBoundaryService.php';
|
||||
$boundaryService = new \DepartmentBoundaryService();
|
||||
$departmentsTouched = $boundaryService->getDepartmentsForSector($coordinates);
|
||||
|
||||
if (empty($departmentsTouched)) {
|
||||
// Si aucun département n'est trouvé par analyse spatiale,
|
||||
// chercher d'abord dans le département de l'entité et ses limitrophes
|
||||
$entityDept = $this->getDepartmentForEntity($entityId);
|
||||
$this->logService->info('[AddressService] Département de l\'entité', [
|
||||
'departement' => $entityDept
|
||||
]);
|
||||
if (!$entityDept) {
|
||||
$this->logService->error('[AddressService] Impossible de déterminer le département de l\'entité', [
|
||||
'entity_id' => $entityId
|
||||
]);
|
||||
throw new RuntimeException("Impossible de déterminer le département");
|
||||
}
|
||||
|
||||
// Obtenir les départements prioritaires (entité + limitrophes)
|
||||
$priorityDepts = $boundaryService->getPriorityDepartments($entityDept);
|
||||
|
||||
// Log pour debug
|
||||
$this->logService->warning('[AddressService] Aucun département trouvé par analyse spatiale', [
|
||||
'departements_prioritaires' => implode(', ', $priorityDepts)
|
||||
]);
|
||||
|
||||
// Utiliser les départements prioritaires pour la recherche
|
||||
$departmentsTouched = [];
|
||||
foreach ($priorityDepts as $deptCode) {
|
||||
$departmentsTouched[] = ['code_dept' => $deptCode];
|
||||
}
|
||||
}
|
||||
|
||||
// Créer le polygone SQL à partir des coordonnées
|
||||
$polygonPoints = [];
|
||||
foreach ($coordinates as $coord) {
|
||||
if (!isset($coord[0]) || !isset($coord[1])) {
|
||||
throw new InvalidArgumentException("Chaque coordonnée doit avoir une latitude et une longitude");
|
||||
}
|
||||
$polygonPoints[] = $coord[1] . ' ' . $coord[0]; // MySQL attend longitude latitude
|
||||
}
|
||||
|
||||
// Fermer le polygone
|
||||
$polygonPoints[] = $polygonPoints[0];
|
||||
|
||||
$polygonString = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
||||
|
||||
// Collecter les adresses de tous les départements touchés
|
||||
$allAddresses = [];
|
||||
|
||||
foreach ($departmentsTouched as $dept) {
|
||||
$deptCode = $dept['code_dept'];
|
||||
$tableName = "cp" . $deptCode;
|
||||
|
||||
try {
|
||||
// Requête pour récupérer les adresses dans le polygone pour ce département
|
||||
$sql = "SELECT
|
||||
id,
|
||||
numero,
|
||||
rue as voie,
|
||||
cp as code_postal,
|
||||
ville as commune,
|
||||
gps_lat as latitude,
|
||||
gps_lng as longitude,
|
||||
x,
|
||||
y,
|
||||
code_insee,
|
||||
nom_ld,
|
||||
ville_acheminement,
|
||||
rue_afnor,
|
||||
source,
|
||||
certification,
|
||||
:dept_code as departement
|
||||
FROM `$tableName`
|
||||
WHERE ST_Contains(
|
||||
ST_GeomFromText(:polygon, 4326),
|
||||
POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8)))
|
||||
)
|
||||
AND gps_lat != ''
|
||||
AND gps_lng != ''";
|
||||
|
||||
$stmt = $this->addressesDb->prepare($sql);
|
||||
$stmt->execute([
|
||||
'polygon' => $polygonString,
|
||||
'dept_code' => $deptCode
|
||||
]);
|
||||
|
||||
$addresses = $stmt->fetchAll();
|
||||
|
||||
// Ajouter les adresses à la collection globale
|
||||
foreach ($addresses as $address) {
|
||||
$allAddresses[] = $address;
|
||||
}
|
||||
|
||||
// Log pour debug
|
||||
$this->logService->info('[AddressService] Recherche dans table', [
|
||||
'table' => $tableName,
|
||||
'departement' => $deptCode,
|
||||
'nb_adresses' => count($addresses)
|
||||
]);
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// Log l'erreur mais continue avec les autres départements
|
||||
$this->logService->error('[AddressService] Erreur SQL', [
|
||||
'table' => $tableName,
|
||||
'departement' => $deptCode,
|
||||
'error' => $e->getMessage(),
|
||||
'code' => $e->getCode()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logService->info('[AddressService] Fin recherche adresses', [
|
||||
'total_adresses' => count($allAddresses)
|
||||
]);
|
||||
return $allAddresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les adresses dans un rayon autour d'un point
|
||||
*
|
||||
* @param float $latitude Latitude du centre
|
||||
* @param float $longitude Longitude du centre
|
||||
* @param float $radiusMeters Rayon en mètres
|
||||
* @param int|null $entityId ID de l'entité (pour déterminer le département)
|
||||
* @return array Array des adresses trouvées
|
||||
*/
|
||||
public function getAddressesInRadius(float $latitude, float $longitude, float $radiusMeters, ?int $entityId = null): array {
|
||||
// Déterminer le département
|
||||
$dept = $this->getDepartmentForEntity($entityId);
|
||||
if (!$dept) {
|
||||
throw new RuntimeException("Impossible de déterminer le département de l'entité");
|
||||
}
|
||||
|
||||
// Nom de la table selon le département
|
||||
$tableName = "cp" . $dept;
|
||||
|
||||
try {
|
||||
// Utiliser ST_Distance_Sphere pour calculer la distance en mètres
|
||||
$sql = "SELECT
|
||||
id,
|
||||
numero,
|
||||
rue as voie,
|
||||
cp as code_postal,
|
||||
ville as commune,
|
||||
gps_lat as latitude,
|
||||
gps_lng as longitude,
|
||||
ST_Distance_Sphere(
|
||||
POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))),
|
||||
ST_GeomFromText(:point, 4326)
|
||||
) as distance
|
||||
FROM `$tableName`
|
||||
WHERE ST_Distance_Sphere(
|
||||
POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8))),
|
||||
ST_GeomFromText(:point, 4326)
|
||||
) <= :radius
|
||||
AND gps_lat != ''
|
||||
AND gps_lng != ''
|
||||
ORDER BY distance";
|
||||
|
||||
$point = "POINT($longitude $latitude)";
|
||||
|
||||
$stmt = $this->addressesDb->prepare($sql);
|
||||
$stmt->execute([
|
||||
'point' => $point,
|
||||
'radius' => $radiusMeters
|
||||
]);
|
||||
|
||||
return $stmt->fetchAll();
|
||||
} catch (PDOException $e) {
|
||||
throw new RuntimeException("Erreur lors de la récupération des adresses dans la table $tableName : " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compte le nombre d'adresses dans un polygone
|
||||
* Gère automatiquement les secteurs multi-départements
|
||||
*
|
||||
* @param array $coordinates Array de coordonnées [[lat, lng], [lat, lng], ...]
|
||||
* @param int|null $entityId ID de l'entité (pour déterminer le département principal)
|
||||
* @return int Nombre d'adresses
|
||||
*/
|
||||
public function countAddressesInPolygon(array $coordinates, ?int $entityId = null): int {
|
||||
// Si pas de connexion à la base d'adresses, retourner 0
|
||||
if (!$this->addressesDb) {
|
||||
error_log("AddressService: Pas de connexion à la base d'adresses, retour de 0 adresses");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (count($coordinates) < 3) {
|
||||
throw new InvalidArgumentException("Un polygone doit avoir au moins 3 points");
|
||||
}
|
||||
|
||||
// D'abord, déterminer tous les départements touchés par ce secteur
|
||||
require_once __DIR__ . '/DepartmentBoundaryService.php';
|
||||
$boundaryService = new \DepartmentBoundaryService();
|
||||
$departmentsTouched = $boundaryService->getDepartmentsForSector($coordinates);
|
||||
|
||||
if (empty($departmentsTouched)) {
|
||||
// Si aucun département n'est trouvé, utiliser le département de l'entité
|
||||
$dept = $this->getDepartmentForEntity($entityId);
|
||||
if (!$dept) {
|
||||
throw new RuntimeException("Impossible de déterminer le département");
|
||||
}
|
||||
$departmentsTouched = [['code_dept' => $dept]];
|
||||
}
|
||||
|
||||
// Créer le polygone SQL à partir des coordonnées
|
||||
$polygonPoints = [];
|
||||
foreach ($coordinates as $coord) {
|
||||
if (!isset($coord[0]) || !isset($coord[1])) {
|
||||
throw new InvalidArgumentException("Chaque coordonnée doit avoir une latitude et une longitude");
|
||||
}
|
||||
$polygonPoints[] = $coord[1] . ' ' . $coord[0]; // MySQL attend longitude latitude
|
||||
}
|
||||
|
||||
// Fermer le polygone
|
||||
$polygonPoints[] = $polygonPoints[0];
|
||||
|
||||
$polygonString = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
||||
|
||||
// Compter les adresses dans tous les départements touchés
|
||||
$totalCount = 0;
|
||||
|
||||
foreach ($departmentsTouched as $dept) {
|
||||
$deptCode = $dept['code_dept'];
|
||||
$tableName = "cp" . $deptCode;
|
||||
|
||||
try {
|
||||
$sql = "SELECT COUNT(*) as count
|
||||
FROM `$tableName`
|
||||
WHERE ST_Contains(
|
||||
ST_GeomFromText(:polygon, 4326),
|
||||
POINT(CAST(gps_lng AS DECIMAL(10,8)), CAST(gps_lat AS DECIMAL(10,8)))
|
||||
)
|
||||
AND gps_lat != ''
|
||||
AND gps_lng != ''";
|
||||
|
||||
$stmt = $this->addressesDb->prepare($sql);
|
||||
$stmt->execute(['polygon' => $polygonString]);
|
||||
|
||||
$result = $stmt->fetch();
|
||||
$deptCount = (int)$result['count'];
|
||||
$totalCount += $deptCount;
|
||||
|
||||
// Log pour debug
|
||||
error_log("Département $deptCode : $deptCount adresses comptées");
|
||||
|
||||
} catch (PDOException $e) {
|
||||
// Log l'erreur mais continue avec les autres départements
|
||||
error_log("Erreur de comptage pour le département $deptCode : " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $totalCount;
|
||||
}
|
||||
}
|
||||
0
api/src/Services/ApiService.php
Normal file → Executable file
0
api/src/Services/ApiService.php
Normal file → Executable file
0
api/src/Services/BackupEncryptionService.php
Normal file → Executable file
0
api/src/Services/BackupEncryptionService.php
Normal file → Executable file
249
api/src/Services/DepartmentBoundaryService.php
Normal file
249
api/src/Services/DepartmentBoundaryService.php
Normal file
@@ -0,0 +1,249 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
class DepartmentBoundaryService {
|
||||
private PDO $db;
|
||||
|
||||
public function __construct() {
|
||||
$this->db = Database::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un polygone (secteur) est entièrement contenu dans un département
|
||||
*
|
||||
* @param array $sectorCoordinates Coordonnées du secteur [[lat, lng], ...]
|
||||
* @param string $departmentCode Code du département (22, 29, etc.)
|
||||
* @return array ['is_contained' => bool, 'message' => string, 'intersecting_departments' => array]
|
||||
*/
|
||||
public function checkSectorInDepartment(array $sectorCoordinates, string $departmentCode): array {
|
||||
if (count($sectorCoordinates) < 3) {
|
||||
return [
|
||||
'is_contained' => false,
|
||||
'message' => 'Un secteur doit avoir au moins 3 points',
|
||||
'intersecting_departments' => []
|
||||
];
|
||||
}
|
||||
|
||||
// Créer le polygone du secteur
|
||||
$polygonPoints = [];
|
||||
foreach ($sectorCoordinates as $coord) {
|
||||
if (!isset($coord[0]) || !isset($coord[1])) {
|
||||
return [
|
||||
'is_contained' => false,
|
||||
'message' => 'Coordonnées invalides',
|
||||
'intersecting_departments' => []
|
||||
];
|
||||
}
|
||||
$polygonPoints[] = $coord[1] . ' ' . $coord[0]; // longitude latitude
|
||||
}
|
||||
|
||||
// Fermer le polygone
|
||||
$polygonPoints[] = $polygonPoints[0];
|
||||
$sectorPolygon = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
||||
|
||||
try {
|
||||
// 1. Vérifier si le secteur est entièrement dans le département cible
|
||||
$sql = "SELECT
|
||||
code_dept,
|
||||
nom_dept,
|
||||
ST_Contains(contour, ST_GeomFromText(:sector_polygon, 4326)) as is_contained,
|
||||
ST_Intersects(contour, ST_GeomFromText(:sector_polygon, 4326)) as intersects
|
||||
FROM x_departements
|
||||
WHERE code = :dept_code
|
||||
AND contour IS NOT NULL";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([
|
||||
'sector_polygon' => $sectorPolygon,
|
||||
'dept_code' => $departmentCode
|
||||
]);
|
||||
|
||||
$targetDept = $stmt->fetch();
|
||||
|
||||
if (!$targetDept) {
|
||||
return [
|
||||
'is_contained' => false,
|
||||
'message' => "Le département $departmentCode n'a pas de contour défini",
|
||||
'intersecting_departments' => []
|
||||
];
|
||||
}
|
||||
|
||||
// 2. Si le secteur n'est pas entièrement contenu, trouver tous les départements qu'il touche
|
||||
if (!$targetDept['is_contained']) {
|
||||
$sql = "SELECT
|
||||
code_dept,
|
||||
nom_dept,
|
||||
ST_Area(ST_Intersection(contour, ST_GeomFromText(:sector_polygon1, 4326))) /
|
||||
ST_Area(ST_GeomFromText(:sector_polygon2, 4326)) * 100 as percentage_overlap
|
||||
FROM x_departements_contours
|
||||
WHERE ST_Intersects(contour, ST_GeomFromText(:sector_polygon3, 4326))
|
||||
ORDER BY percentage_overlap DESC";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([
|
||||
'sector_polygon1' => $sectorPolygon,
|
||||
'sector_polygon2' => $sectorPolygon,
|
||||
'sector_polygon3' => $sectorPolygon
|
||||
]);
|
||||
|
||||
$intersectingDepts = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
// Formater le message
|
||||
$deptsList = array_map(function($d) {
|
||||
return sprintf("%s (%s) : %.1f%%",
|
||||
$d['nom_dept'],
|
||||
$d['code_dept'],
|
||||
$d['percentage_overlap']
|
||||
);
|
||||
}, $intersectingDepts);
|
||||
|
||||
return [
|
||||
'is_contained' => false,
|
||||
'message' => "Le secteur déborde du département {$targetDept['nom_dept']}. Il est à cheval sur : " . implode(', ', $deptsList),
|
||||
'intersecting_departments' => $intersectingDepts
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'is_contained' => true,
|
||||
'message' => "Le secteur est entièrement contenu dans le département {$targetDept['nom_dept']}",
|
||||
'intersecting_departments' => [$targetDept]
|
||||
];
|
||||
|
||||
} catch (\PDOException $e) {
|
||||
throw new RuntimeException("Erreur lors de la vérification des limites départementales : " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les départements qui intersectent avec un secteur
|
||||
*
|
||||
* @param array $sectorCoordinates Coordonnées du secteur [[lat, lng], ...]
|
||||
* @return array Liste des départements avec leur pourcentage de recouvrement
|
||||
*/
|
||||
public function getDepartmentsForSector(array $sectorCoordinates): array {
|
||||
if (count($sectorCoordinates) < 3) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Créer le polygone du secteur
|
||||
$polygonPoints = [];
|
||||
foreach ($sectorCoordinates as $coord) {
|
||||
$polygonPoints[] = $coord[1] . ' ' . $coord[0]; // longitude latitude
|
||||
}
|
||||
$polygonPoints[] = $polygonPoints[0];
|
||||
$sectorPolygon = 'POLYGON((' . implode(',', $polygonPoints) . '))';
|
||||
|
||||
try {
|
||||
$sql = "SELECT
|
||||
code_dept,
|
||||
nom_dept,
|
||||
ST_Area(ST_Intersection(contour, ST_GeomFromText(:sector_polygon1, 4326))) /
|
||||
ST_Area(ST_GeomFromText(:sector_polygon2, 4326)) * 100 as percentage_overlap
|
||||
FROM x_departements_contours
|
||||
WHERE ST_Intersects(contour, ST_GeomFromText(:sector_polygon3, 4326))
|
||||
ORDER BY percentage_overlap DESC";
|
||||
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute([
|
||||
'sector_polygon1' => $sectorPolygon,
|
||||
'sector_polygon2' => $sectorPolygon,
|
||||
'sector_polygon3' => $sectorPolygon
|
||||
]);
|
||||
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
} catch (\PDOException $e) {
|
||||
throw new RuntimeException("Erreur lors de la recherche des départements : " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si les contours des départements sont chargés
|
||||
*
|
||||
* @return array ['loaded' => bool, 'count' => int, 'missing' => array]
|
||||
*/
|
||||
public function checkDepartmentContoursStatus(): array {
|
||||
try {
|
||||
// Compter les départements avec contours
|
||||
$sql = "SELECT COUNT(*) as count FROM x_departements_contours";
|
||||
$stmt = $this->db->query($sql);
|
||||
$count = $stmt->fetch()['count'];
|
||||
|
||||
// Récupérer la liste des départements utilisés dans les entités
|
||||
$sql = "SELECT DISTINCT departement
|
||||
FROM entites
|
||||
WHERE departement IS NOT NULL
|
||||
ORDER BY departement";
|
||||
$stmt = $this->db->query($sql);
|
||||
$usedDepts = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
|
||||
// Vérifier lesquels ont des contours
|
||||
$sql = "SELECT code_dept FROM x_departements_contours WHERE code_dept IN ('" . implode("','", $usedDepts) . "')";
|
||||
$stmt = $this->db->query($sql);
|
||||
$loadedDepts = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
|
||||
$missingDepts = array_diff($usedDepts, $loadedDepts);
|
||||
|
||||
return [
|
||||
'loaded' => count($missingDepts) === 0,
|
||||
'count' => $count,
|
||||
'total_used' => count($usedDepts),
|
||||
'missing' => array_values($missingDepts)
|
||||
];
|
||||
|
||||
} catch (\PDOException $e) {
|
||||
return [
|
||||
'loaded' => false,
|
||||
'count' => 0,
|
||||
'total_used' => 0,
|
||||
'missing' => []
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les départements limitrophes d'un département donné
|
||||
*
|
||||
* @param string $departmentCode Code du département
|
||||
* @return array Array des codes départements limitrophes
|
||||
*/
|
||||
public function getAdjacentDepartments(string $departmentCode): array {
|
||||
try {
|
||||
$sql = "SELECT dept_limitrophes FROM x_departements WHERE code = :dept_code AND chk_active = 1";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->execute(['dept_code' => $departmentCode]);
|
||||
|
||||
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if (!$result || empty($result['dept_limitrophes'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Convertir la chaîne CSV en array
|
||||
return array_map('trim', explode(',', $result['dept_limitrophes']));
|
||||
|
||||
} catch (PDOException $e) {
|
||||
error_log("Erreur lors de la récupération des départements limitrophes : " . $e->getMessage());
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les départements à prioriser pour la recherche
|
||||
* (département de l'entité + ses limitrophes)
|
||||
*
|
||||
* @param string $entityDepartment Code du département de l'entité
|
||||
* @return array Array des codes départements à prioriser
|
||||
*/
|
||||
public function getPriorityDepartments(string $entityDepartment): array {
|
||||
$priorityDepts = [$entityDepartment]; // Commencer par le département de l'entité
|
||||
|
||||
// Ajouter les départements limitrophes
|
||||
$adjacentDepts = $this->getAdjacentDepartments($entityDepartment);
|
||||
$priorityDepts = array_merge($priorityDepts, $adjacentDepts);
|
||||
|
||||
// Retourner sans doublons
|
||||
return array_unique($priorityDepts);
|
||||
}
|
||||
}
|
||||
0
api/src/Services/EmailTemplates.php
Normal file → Executable file
0
api/src/Services/EmailTemplates.php
Normal file → Executable file
0
api/src/Services/ExportService.php
Normal file → Executable file
0
api/src/Services/ExportService.php
Normal file → Executable file
0
api/src/Services/FileService.php
Normal file → Executable file
0
api/src/Services/FileService.php
Normal file → Executable file
48
api/src/Services/LogService.php
Normal file → Executable file
48
api/src/Services/LogService.php
Normal file → Executable file
@@ -25,6 +25,19 @@ class LogService {
|
||||
if ($clientType === 'mobile' && isset($clientInfo['appIdentifier'])) {
|
||||
$defaultMetadata['app_identifier'] = $clientInfo['appIdentifier'];
|
||||
}
|
||||
|
||||
// Ajouter les informations de session si disponibles
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
if (isset($_SESSION['user_id'])) {
|
||||
$defaultMetadata['user_id'] = $_SESSION['user_id'];
|
||||
}
|
||||
if (isset($_SESSION['entity_id'])) {
|
||||
$defaultMetadata['entity_id'] = $_SESSION['entity_id'];
|
||||
}
|
||||
if (isset($_SESSION['operation_id'])) {
|
||||
$defaultMetadata['operation_id'] = $_SESSION['operation_id'];
|
||||
}
|
||||
}
|
||||
|
||||
$metadata = array_merge_recursive($defaultMetadata, $metadata);
|
||||
|
||||
@@ -73,15 +86,31 @@ class LogService {
|
||||
// timestamp;browser.name@browser.version;os.name@os.version;client_type;$metadata;$message
|
||||
$timestamp = date('Y-m-d\TH:i:s');
|
||||
$browserInfo = $clientInfo['browser']['name'] . '@' . $clientInfo['browser']['version'];
|
||||
$osInfo = $clientInfo['os']['name'] . '@' . $clientInfo['os']['version'];
|
||||
|
||||
// Ne pas afficher l'OS s'il est unknown
|
||||
$osInfo = '';
|
||||
if ($clientInfo['os']['name'] !== 'unknown' && $clientInfo['os']['version'] !== 'unknown') {
|
||||
$osInfo = $clientInfo['os']['name'] . '@' . $clientInfo['os']['version'];
|
||||
}
|
||||
|
||||
// Extraire le niveau de log
|
||||
$level = isset($metadata['level']) ? (is_array($metadata['level']) ? 'info' : $metadata['level']) : 'info';
|
||||
|
||||
// Préparer les métadonnées supplémentaires (exclure celles déjà incluses dans le format)
|
||||
$additionalMetadata = [];
|
||||
|
||||
// Ajouter user_id, entity_id et operation_id en premier s'ils existent
|
||||
$priorityKeys = ['user_id', 'entity_id', 'operation_id'];
|
||||
foreach ($priorityKeys as $key) {
|
||||
if (isset($metadata[$key]) && !is_array($metadata[$key])) {
|
||||
$additionalMetadata[$key] = $metadata[$key];
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter les autres métadonnées
|
||||
foreach ($metadata as $key => $value) {
|
||||
if (!in_array($key, ['browser', 'os', 'client_type', 'side', 'version', 'level', 'environment', 'client'])) {
|
||||
if (!in_array($key, ['browser', 'os', 'client_type', 'side', 'version', 'level', 'environment', 'client'])
|
||||
&& !in_array($key, $priorityKeys)) {
|
||||
if (is_array($value)) {
|
||||
$additionalMetadata[$key] = json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
@@ -114,4 +143,19 @@ class LogService {
|
||||
error_log("Erreur lors de l'écriture des logs: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function info(string $message, array $metadata = []): void {
|
||||
$metadata['level'] = 'info';
|
||||
self::log($message, $metadata);
|
||||
}
|
||||
|
||||
public function warning(string $message, array $metadata = []): void {
|
||||
$metadata['level'] = 'warning';
|
||||
self::log($message, $metadata);
|
||||
}
|
||||
|
||||
public function error(string $message, array $metadata = []): void {
|
||||
$metadata['level'] = 'error';
|
||||
self::log($message, $metadata);
|
||||
}
|
||||
}
|
||||
|
||||
0
api/src/Services/OperationDataService.php
Normal file → Executable file
0
api/src/Services/OperationDataService.php
Normal file → Executable file
Reference in New Issue
Block a user