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:
pierre
2025-08-07 11:01:45 +02:00
parent 6a609fb467
commit 599b9fcda0
662 changed files with 213221 additions and 174243 deletions

162
api/src/Controllers/UserController.php Normal file → Executable file
View File

@@ -550,28 +550,27 @@ class UserController {
// ——— Gestion du transfert éventuel ———
$transferTo = isset($_GET['transfer_to']) ? trim($_GET['transfer_to']) : null;
$operationId = isset($_GET['operation_id']) ? trim($_GET['operation_id']) : null;
if (($transferTo && !$operationId) || (!$transferTo && $operationId)) {
Response::json([
'status' => 'error',
'message' => "Il faut fournir transfer_to ET operation_id ou aucun des deux"
], 400);
return;
}
if ($transferTo && $operationId) {
if ($transferTo) {
try {
// Transférer TOUS les passages de l'utilisateur vers l'utilisateur désigné
$stmt3 = $this->db->prepare('
UPDATE passages
UPDATE ope_pass
SET fk_user = :new_user_id
WHERE fk_user = :delete_user_id
AND fk_operation = :operation_id
');
$stmt3->execute([
'new_user_id' => $transferTo,
'delete_user_id' => $id,
'operation_id' => $operationId
'delete_user_id' => $id
]);
$transferredCount = $stmt3->rowCount();
LogService::log('Passages transférés avant suppression utilisateur', [
'level' => 'info',
'from_user' => $id,
'to_user' => $transferTo,
'passages_transferred' => $transferredCount
]);
} catch (PDOException $e) {
Response::json([
@@ -589,7 +588,9 @@ class UserController {
$stmtOpeUsers = $this->db->prepare('DELETE FROM ope_users WHERE fk_user = ?');
$stmtOpeUsers->execute([$id]);
// Ici éventuellement : d'autres suppressions en cascade si besoin
// Supprimer les enregistrements dépendants dans ope_users_sectors
$stmtOpeUsersSectors = $this->db->prepare('DELETE FROM ope_users_sectors WHERE fk_user = ?');
$stmtOpeUsersSectors->execute([$id]);
$stmt = $this->db->prepare('DELETE FROM users WHERE id = ?');
$stmt->execute([$id]);
@@ -606,7 +607,7 @@ class UserController {
'level' => 'info',
'deletedBy' => $currentUserId,
'userId' => $id,
'passage_transfer' => $transferTo && $operationId ? "Vers utilisateur $transferTo pour operation $operationId" : 'Aucun'
'passage_transfer' => $transferTo ? "Tous les passages transférés vers utilisateur $transferTo" : 'Aucun transfert'
]);
Response::json([
@@ -626,6 +627,135 @@ class UserController {
}
}
public function resetPassword(string $id): void {
Session::requireAuth();
$currentUserId = Session::getUserId();
// Récupérer les infos de l'utilisateur courant
$stmt = $this->db->prepare('SELECT fk_role, fk_entite, chk_active FROM users WHERE id = ?');
$stmt->execute([$currentUserId]);
$currentUser = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$currentUser) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur courant non trouvé'
], 403);
return;
}
// Vérifier que l'utilisateur courant est actif
if ($currentUser['chk_active'] != 1) {
Response::json([
'status' => 'error',
'message' => 'Votre compte n\'est pas actif'
], 403);
return;
}
$userRole = (int)$currentUser['fk_role'];
$userEntite = $currentUser['fk_entite'];
// Récupérer l'utilisateur cible
$stmt = $this->db->prepare('
SELECT id, encrypted_email, encrypted_name, fk_entite, chk_active
FROM users
WHERE id = ?
');
$stmt->execute([$id]);
$targetUser = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$targetUser) {
Response::json([
'status' => 'error',
'message' => 'Utilisateur non trouvé'
], 404);
return;
}
// Vérifier que l'utilisateur est actif
if ($targetUser['chk_active'] != 1) {
Response::json([
'status' => 'error',
'message' => 'L\'utilisateur n\'est pas actif'
], 400);
return;
}
// Contrôle des droits selon le rôle
if ($userRole === 1) {
// Role 1 : peut uniquement réinitialiser son propre mot de passe
if ($currentUserId != $id) {
Response::json([
'status' => 'error',
'message' => 'Vous ne pouvez réinitialiser que votre propre mot de passe'
], 403);
return;
}
} elseif ($userRole === 2) {
// Role 2 : peut réinitialiser les mots de passe de sa propre entité
if ($userEntite != $targetUser['fk_entite']) {
Response::json([
'status' => 'error',
'message' => 'Vous ne pouvez réinitialiser que les mots de passe des utilisateurs de votre entité'
], 403);
return;
}
}
// Role > 2 : peut tout faire
try {
// Déchiffrement des données
$email = ApiService::decryptSearchableData($targetUser['encrypted_email']);
$name = ApiService::decryptData($targetUser['encrypted_name']);
// Génération d'un nouveau mot de passe sécurisé
$newPassword = ApiService::generateSecurePassword();
$passwordHash = password_hash($newPassword, PASSWORD_DEFAULT);
// Mise à jour du mot de passe en base de données
$updateStmt = $this->db->prepare('
UPDATE users
SET user_pass_hash = :password,
updated_at = NOW(),
fk_user_modif = :modifier_id
WHERE id = :id
');
$updateStmt->execute([
'password' => $passwordHash,
'modifier_id' => $currentUserId,
'id' => $id
]);
// Envoi de l'email avec le nouveau mot de passe
ApiService::sendEmail($email, $name, 'password_reset', ['password' => $newPassword]);
LogService::log('Mot de passe réinitialisé', [
'level' => 'info',
'resetBy' => $currentUserId,
'userId' => $id,
'email' => $email
]);
Response::json([
'status' => 'success',
'message' => 'Mot de passe réinitialisé avec succès. Un email a été envoyé à l\'utilisateur.'
]);
} catch (PDOException $e) {
LogService::log('Erreur lors de la réinitialisation du mot de passe', [
'level' => 'error',
'error' => $e->getMessage(),
'userId' => $id
]);
Response::json([
'status' => 'error',
'message' => 'Erreur serveur'
], 500);
}
}
// Méthodes auxiliaires
private function validateUpdateData(array $data): ?string {
// Validation de l'email