- 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>
1517 lines
57 KiB
PHP
Executable File
1517 lines
57 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Controllers;
|
|
|
|
require_once __DIR__ . '/../Services/LogService.php';
|
|
require_once __DIR__ . '/../Services/ExportService.php';
|
|
require_once __DIR__ . '/../Services/ApiService.php';
|
|
require_once __DIR__ . '/../Services/OperationDataService.php';
|
|
|
|
use PDO;
|
|
use PDOException;
|
|
use Database;
|
|
use AppConfig;
|
|
use Request;
|
|
use Response;
|
|
use Session;
|
|
use LogService;
|
|
use ExportService;
|
|
use ApiService;
|
|
use OperationDataService;
|
|
use Exception;
|
|
use DateTime;
|
|
|
|
class OperationController {
|
|
private PDO $db;
|
|
private AppConfig $appConfig;
|
|
|
|
public function __construct() {
|
|
$this->db = Database::getInstance();
|
|
$this->appConfig = AppConfig::getInstance();
|
|
}
|
|
|
|
/**
|
|
* Récupère l'entité de l'utilisateur connecté
|
|
*
|
|
* @param int $userId ID de l'utilisateur
|
|
* @return int|null ID de l'entité ou null si non trouvé
|
|
*/
|
|
private function getUserEntiteId(int $userId): ?int {
|
|
try {
|
|
$stmt = $this->db->prepare('SELECT fk_entite FROM users WHERE id = ?');
|
|
$stmt->execute([$userId]);
|
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
return $user ? (int)$user['fk_entite'] : null;
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de la récupération de l\'entité utilisateur', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'userId' => $userId
|
|
]);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Valide les données d'une opération
|
|
*
|
|
* @param array $data Données à valider
|
|
* @param int $entiteId ID de l'entité
|
|
* @param int|null $operationId ID de l'opération (pour update)
|
|
* @return array|null Erreurs de validation ou null
|
|
*/
|
|
private function validateOperationData(array $data, int $entiteId, ?int $operationId = null): ?array {
|
|
$errors = [];
|
|
|
|
// Validation du libellé (accepter 'name' ou 'libelle')
|
|
$libelle = $data['libelle'] ?? $data['name'] ?? null;
|
|
if (!$libelle || empty(trim($libelle))) {
|
|
$errors[] = 'Le nom de l\'opération est obligatoire';
|
|
} else {
|
|
$libelle = trim($libelle);
|
|
if (strlen($libelle) < 5) {
|
|
$errors[] = 'Le nom de l\'opération doit contenir au moins 5 caractères';
|
|
}
|
|
if (strlen($libelle) > 75) {
|
|
$errors[] = 'Le nom de l\'opération ne peut pas dépasser 75 caractères';
|
|
}
|
|
|
|
// Vérifier l'unicité du nom dans l'entité
|
|
$sql = 'SELECT COUNT(*) as count FROM operations WHERE fk_entite = ? AND libelle = ? AND chk_active = 1';
|
|
$params = [$entiteId, $libelle];
|
|
|
|
if ($operationId) {
|
|
$sql .= ' AND id != ?';
|
|
$params[] = $operationId;
|
|
}
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute($params);
|
|
$result = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($result && $result['count'] > 0) {
|
|
$errors[] = 'Une opération avec ce nom existe déjà dans votre entité';
|
|
}
|
|
}
|
|
|
|
// Validation des dates
|
|
if (!isset($data['date_deb']) || empty($data['date_deb'])) {
|
|
$errors[] = 'La date de début est obligatoire';
|
|
}
|
|
|
|
if (!isset($data['date_fin']) || empty($data['date_fin'])) {
|
|
$errors[] = 'La date de fin est obligatoire';
|
|
}
|
|
|
|
if (
|
|
isset($data['date_deb']) && isset($data['date_fin']) &&
|
|
!empty($data['date_deb']) && !empty($data['date_fin'])
|
|
) {
|
|
|
|
$dateDeb = DateTime::createFromFormat('Y-m-d', $data['date_deb']);
|
|
$dateFin = DateTime::createFromFormat('Y-m-d', $data['date_fin']);
|
|
|
|
if (!$dateDeb) {
|
|
$errors[] = 'Format de date de début invalide (YYYY-MM-DD attendu)';
|
|
}
|
|
|
|
if (!$dateFin) {
|
|
$errors[] = 'Format de date de fin invalide (YYYY-MM-DD attendu)';
|
|
}
|
|
|
|
if ($dateDeb && $dateFin && $dateFin <= $dateDeb) {
|
|
$errors[] = 'La date de fin doit être postérieure à la date de début';
|
|
}
|
|
}
|
|
|
|
return empty($errors) ? null : $errors;
|
|
}
|
|
|
|
/**
|
|
* Récupère toutes les opérations de l'entité de l'utilisateur
|
|
*/
|
|
public function getOperations(): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$entiteId = $this->getUserEntiteId($userId);
|
|
if (!$entiteId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Entité non trouvée pour cet utilisateur'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, libelle, date_deb, date_fin, chk_distinct_sectors,
|
|
created_at, updated_at, chk_active
|
|
FROM operations
|
|
WHERE fk_entite = ?
|
|
ORDER BY chk_active DESC, created_at DESC
|
|
');
|
|
|
|
$stmt->execute([$entiteId]);
|
|
$operations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'operations' => $operations
|
|
], 200);
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de la récupération des opérations', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la récupération des opérations'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Récupère une opération spécifique par son ID
|
|
*/
|
|
public function getOperationById(string $id): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$entiteId = $this->getUserEntiteId($userId);
|
|
if (!$entiteId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Entité non trouvée pour cet utilisateur'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$operationId = (int)$id;
|
|
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, libelle, date_deb, date_fin, chk_distinct_sectors,
|
|
created_at, updated_at, chk_active
|
|
FROM operations
|
|
WHERE id = ? AND fk_entite = ?
|
|
');
|
|
|
|
$stmt->execute([$operationId, $entiteId]);
|
|
$operation = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$operation) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Opération non trouvée'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'operation' => $operation
|
|
], 200);
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de la récupération de l\'opération', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $id,
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la récupération de l\'opération'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Crée une nouvelle opération avec duplication des données de l'opération active précédente
|
|
*/
|
|
public function createOperation(): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$entiteId = $this->getUserEntiteId($userId);
|
|
if (!$entiteId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Entité non trouvée pour cet utilisateur'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$data = Request::getJson();
|
|
|
|
// Validation des données
|
|
$errors = $this->validateOperationData($data, $entiteId);
|
|
if ($errors) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreurs de validation',
|
|
'errors' => $errors
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
$this->db->beginTransaction();
|
|
|
|
// Étape 1 : Récupérer l'id de l'opération active actuelle (oldOpeId)
|
|
$stmt = $this->db->prepare('
|
|
SELECT id FROM operations
|
|
WHERE fk_entite = ? AND chk_active = 1
|
|
LIMIT 1
|
|
');
|
|
$stmt->execute([$entiteId]);
|
|
$oldOperation = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
$oldOpeId = $oldOperation ? (int)$oldOperation['id'] : null;
|
|
|
|
LogService::log('Étape 1 : Récupération opération active', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'entiteId' => $entiteId,
|
|
'oldOpeId' => $oldOpeId
|
|
]);
|
|
|
|
// Étape 2 : Créer la nouvelle opération (newOpeId) - pas encore active
|
|
$stmt = $this->db->prepare('
|
|
INSERT INTO operations (
|
|
fk_entite, libelle, date_deb, date_fin,
|
|
chk_distinct_sectors, fk_user_creat, chk_active
|
|
) VALUES (?, ?, ?, ?, ?, ?, 0)
|
|
');
|
|
|
|
$stmt->execute([
|
|
$entiteId,
|
|
trim($data['name']),
|
|
$data['date_deb'],
|
|
$data['date_fin'],
|
|
isset($data['chk_distinct_sectors']) ? (int)$data['chk_distinct_sectors'] : 0,
|
|
$userId
|
|
]);
|
|
|
|
$newOpeId = (int)$this->db->lastInsertId();
|
|
|
|
LogService::log('Étape 2 : Création nouvelle opération', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'entiteId' => $entiteId,
|
|
'newOpeId' => $newOpeId,
|
|
'libelle' => trim($data['name'])
|
|
]);
|
|
|
|
// Étape 3 : Insérer tous les users actifs de l'entité dans ope_users avec newOpeId
|
|
$stmt = $this->db->prepare('
|
|
INSERT INTO ope_users (fk_operation, fk_user, fk_role, first_name, encrypted_name, sect_name, fk_user_creat)
|
|
SELECT ?, id, fk_role, first_name, encrypted_name, sect_name, ?
|
|
FROM users
|
|
WHERE fk_entite = ? AND chk_active = 1
|
|
');
|
|
$stmt->execute([$newOpeId, $userId, $entiteId]);
|
|
$insertedUsers = $stmt->rowCount();
|
|
|
|
LogService::log('Étape 3 : Insertion users actifs', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'entiteId' => $entiteId,
|
|
'newOpeId' => $newOpeId,
|
|
'insertedUsers' => $insertedUsers
|
|
]);
|
|
|
|
// Étape 4 : Si oldOpeId existe, dupliquer les secteurs et données associées
|
|
$duplicatedSectors = 0;
|
|
$duplicatedUsersSectors = 0;
|
|
$duplicatedPassages = 0;
|
|
|
|
if ($oldOpeId) {
|
|
// Étape 4.1 : Récupérer tous les secteurs de l'ancienne opération
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, libelle, sector, color
|
|
FROM ope_sectors
|
|
WHERE fk_operation = ? AND chk_active = 1
|
|
');
|
|
$stmt->execute([$oldOpeId]);
|
|
$oldSectors = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
foreach ($oldSectors as $oldSector) {
|
|
$oldSectId = (int)$oldSector['id'];
|
|
|
|
// Étape 4.2 : Dupliquer le secteur avec newOpeId
|
|
$stmt = $this->db->prepare('
|
|
INSERT INTO ope_sectors (fk_operation, fk_old_sector, libelle, sector, color, fk_user_creat)
|
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
');
|
|
$stmt->execute([
|
|
$newOpeId,
|
|
$oldSectId,
|
|
$oldSector['libelle'],
|
|
$oldSector['sector'],
|
|
$oldSector['color'],
|
|
$userId
|
|
]);
|
|
$newSectId = (int)$this->db->lastInsertId();
|
|
$duplicatedSectors++;
|
|
|
|
// Étape 4.3 : Dupliquer les users_sectors en vérifiant que fk_user existe dans ope_users
|
|
$stmt = $this->db->prepare('
|
|
INSERT INTO ope_users_sectors (fk_operation, fk_user, fk_sector, fk_user_creat)
|
|
SELECT ?, ous.fk_user, ?, ?
|
|
FROM ope_users_sectors ous
|
|
INNER JOIN ope_users ou ON ou.fk_user = ous.fk_user AND ou.fk_operation = ?
|
|
WHERE ous.fk_operation = ? AND ous.fk_sector = ? AND ous.chk_active = 1
|
|
');
|
|
$stmt->execute([$newOpeId, $newSectId, $userId, $newOpeId, $oldOpeId, $oldSectId]);
|
|
$duplicatedUsersSectors += $stmt->rowCount();
|
|
|
|
// Étape 4.4 : Dupliquer les passages avec les valeurs par défaut spécifiées
|
|
$stmt = $this->db->prepare('
|
|
INSERT INTO ope_pass (
|
|
fk_operation, fk_sector, fk_user, fk_adresse, numero, rue, rue_bis, ville,
|
|
fk_habitat, appt, niveau, residence, gps_lat, gps_lng, encrypted_name,
|
|
fk_type, passed_at, montant, fk_type_reglement, chk_email_sent, chk_striped,
|
|
docremis, nb_passages, chk_map_create, chk_mobile, chk_synchro, anomalie,
|
|
fk_user_creat, chk_active
|
|
)
|
|
SELECT
|
|
?, ?, fk_user, fk_adresse, numero, rue, rue_bis, ville,
|
|
fk_habitat, appt, niveau, residence, gps_lat, gps_lng, encrypted_name,
|
|
2, NULL, 0, 4, 0, 0, 0, 1, 0, 0, 1, 0, ?, 1
|
|
FROM ope_pass
|
|
WHERE fk_operation = ? AND fk_sector = ? AND chk_active = 1
|
|
');
|
|
$stmt->execute([$newOpeId, $newSectId, $userId, $oldOpeId, $oldSectId]);
|
|
$duplicatedPassages += $stmt->rowCount();
|
|
}
|
|
|
|
LogService::log('Étape 4 : Duplication données anciennes opération', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'oldOpeId' => $oldOpeId,
|
|
'newOpeId' => $newOpeId,
|
|
'duplicatedSectors' => $duplicatedSectors,
|
|
'duplicatedUsersSectors' => $duplicatedUsersSectors,
|
|
'duplicatedPassages' => $duplicatedPassages
|
|
]);
|
|
}
|
|
|
|
// Étape 5 : Désactiver l'ancienne opération
|
|
if ($oldOpeId) {
|
|
$stmt = $this->db->prepare('
|
|
UPDATE operations
|
|
SET chk_active = 0, updated_at = NOW(), fk_user_modif = ?
|
|
WHERE id = ?
|
|
');
|
|
$stmt->execute([$userId, $oldOpeId]);
|
|
|
|
LogService::log('Étape 5 : Désactivation ancienne opération', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'oldOpeId' => $oldOpeId
|
|
]);
|
|
}
|
|
|
|
// Étape 6 : Activer la nouvelle opération
|
|
$stmt = $this->db->prepare('
|
|
UPDATE operations
|
|
SET chk_active = 1, updated_at = NOW(), fk_user_modif = ?
|
|
WHERE id = ?
|
|
');
|
|
$stmt->execute([$userId, $newOpeId]);
|
|
|
|
LogService::log('Étape 6 : Activation nouvelle opération', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'newOpeId' => $newOpeId
|
|
]);
|
|
|
|
$this->db->commit();
|
|
|
|
// Étape 7 : Préparer la réponse avec les groupes JSON
|
|
$response = OperationDataService::prepareOperationResponse($this->db, $newOpeId, $entiteId);
|
|
|
|
LogService::log('Création opération terminée avec succès', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'entiteId' => $entiteId,
|
|
'newOpeId' => $newOpeId,
|
|
'oldOpeId' => $oldOpeId,
|
|
'stats' => [
|
|
'insertedUsers' => $insertedUsers,
|
|
'duplicatedSectors' => $duplicatedSectors,
|
|
'duplicatedUsersSectors' => $duplicatedUsersSectors,
|
|
'duplicatedPassages' => $duplicatedPassages
|
|
]
|
|
]);
|
|
|
|
Response::json($response, 201);
|
|
} catch (Exception $e) {
|
|
$this->db->rollBack();
|
|
|
|
LogService::log('Erreur lors de la création de l\'opération', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la création de l\'opération'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Met à jour une opération (uniquement l'opération active)
|
|
*/
|
|
public function updateOperation(string $id): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$entiteId = $this->getUserEntiteId($userId);
|
|
if (!$entiteId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Entité non trouvée pour cet utilisateur'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$operationId = (int)$id;
|
|
$data = Request::getJson();
|
|
|
|
// Étape 1: Vérifier que l'opération existe (sans filtrer par entité d'abord)
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, fk_entite, chk_active
|
|
FROM operations
|
|
WHERE id = ?
|
|
');
|
|
|
|
$stmt->execute([$operationId]);
|
|
$operation = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$operation) {
|
|
LogService::log('Tentative de mise à jour d\'une opération inexistante', [
|
|
'level' => 'warning',
|
|
'userId' => $userId,
|
|
'operationId' => $operationId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Opération non trouvée'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
// Étape 2: Vérifier la cohérence du fk_entite si fourni dans le JSON
|
|
if (isset($data['fk_entite'])) {
|
|
$receivedEntiteId = (int)$data['fk_entite'];
|
|
$operationEntiteId = (int)$operation['fk_entite'];
|
|
|
|
if ($receivedEntiteId !== $operationEntiteId) {
|
|
LogService::log('Incohérence détectée entre fk_entite reçu et celui de l\'opération', [
|
|
'level' => 'warning',
|
|
'userId' => $userId,
|
|
'operationId' => $operationId,
|
|
'receivedEntiteId' => $receivedEntiteId,
|
|
'operationEntiteId' => $operationEntiteId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Incohérence détectée : l\'opération n\'appartient pas à l\'entité spécifiée'
|
|
], 400);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Étape 3: Vérifier que l'utilisateur a accès à l'entité de l'opération
|
|
$operationEntiteId = (int)$operation['fk_entite'];
|
|
if ($operationEntiteId !== $entiteId) {
|
|
LogService::log('Tentative d\'accès à une opération d\'une autre entité', [
|
|
'level' => 'warning',
|
|
'userId' => $userId,
|
|
'userEntiteId' => $entiteId,
|
|
'operationEntiteId' => $operationEntiteId,
|
|
'operationId' => $operationId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous n\'avez pas accès à cette entité'
|
|
], 403);
|
|
return;
|
|
}
|
|
|
|
// Étape 4: Vérifier que l'opération est active
|
|
if (!$operation['chk_active']) {
|
|
LogService::log('Tentative de modification d\'une opération inactive', [
|
|
'level' => 'warning',
|
|
'userId' => $userId,
|
|
'operationId' => $operationId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Seule l\'opération active peut être modifiée'
|
|
], 403);
|
|
return;
|
|
}
|
|
|
|
// Validation des données
|
|
$errors = $this->validateOperationData($data, $entiteId, $operationId);
|
|
if ($errors) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreurs de validation',
|
|
'errors' => $errors
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
// Mettre à jour l'opération
|
|
$stmt = $this->db->prepare('
|
|
UPDATE operations
|
|
SET libelle = ?, date_deb = ?, date_fin = ?,
|
|
chk_distinct_sectors = ?, updated_at = NOW(), fk_user_modif = ?
|
|
WHERE id = ?
|
|
');
|
|
|
|
$libelle = trim($data['libelle'] ?? $data['name']);
|
|
$stmt->execute([
|
|
$libelle,
|
|
$data['date_deb'],
|
|
$data['date_fin'],
|
|
isset($data['chk_distinct_sectors']) ? (int)$data['chk_distinct_sectors'] : 0,
|
|
$userId,
|
|
$operationId
|
|
]);
|
|
|
|
LogService::log('Mise à jour d\'une opération', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'entiteId' => $entiteId,
|
|
'operationId' => $operationId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'message' => 'Opération mise à jour avec succès'
|
|
], 200);
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de la mise à jour de l\'opération', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $id,
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la mise à jour de l\'opération'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Désactive une opération
|
|
*/
|
|
public function deleteOperation(string $id): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$operationId = (int)$id;
|
|
|
|
// Récupérer les informations de l'utilisateur (rôle et entité)
|
|
$stmt = $this->db->prepare('SELECT fk_entite, fk_role FROM users WHERE id = ?');
|
|
$stmt->execute([$userId]);
|
|
$user = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$user) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Utilisateur non trouvé'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$userEntiteId = (int)$user['fk_entite'];
|
|
$userRole = (int)$user['fk_role'];
|
|
|
|
// Vérifier que l'utilisateur a un rôle > 1 (pas un simple utilisateur)
|
|
if ($userRole <= 1) {
|
|
LogService::log('Tentative de suppression d\'opération avec rôle insuffisant', [
|
|
'level' => 'warning',
|
|
'userId' => $userId,
|
|
'userRole' => $userRole,
|
|
'operationId' => $operationId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous n\'avez pas les droits suffisants pour supprimer une opération'
|
|
], 403);
|
|
return;
|
|
}
|
|
|
|
// Récupérer les informations de l'opération
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, fk_entite, chk_active
|
|
FROM operations
|
|
WHERE id = ?
|
|
');
|
|
|
|
$stmt->execute([$operationId]);
|
|
$operation = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$operation) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Opération non trouvée'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$operationEntiteId = (int)$operation['fk_entite'];
|
|
|
|
// Si l'utilisateur a le rôle 2, vérifier qu'il appartient à la même entité que l'opération
|
|
if ($userRole == 2 && $userEntiteId !== $operationEntiteId) {
|
|
LogService::log('Tentative de suppression d\'opération d\'une autre entité', [
|
|
'level' => 'warning',
|
|
'userId' => $userId,
|
|
'userRole' => $userRole,
|
|
'userEntiteId' => $userEntiteId,
|
|
'operationEntiteId' => $operationEntiteId,
|
|
'operationId' => $operationId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous ne pouvez supprimer que les opérations de votre entité'
|
|
], 403);
|
|
return;
|
|
}
|
|
|
|
// Les utilisateurs avec rôle > 2 (super admin, etc.) peuvent supprimer toutes les opérations
|
|
// Les utilisateurs avec rôle 2 ne peuvent supprimer que les opérations de leur entité
|
|
|
|
$operationActive = (bool)$operation['chk_active'];
|
|
|
|
// Créer un export complet automatique avant suppression (Excel + JSON)
|
|
try {
|
|
$exportService = new ExportService();
|
|
|
|
// Générer l'export Excel
|
|
$excelFile = $exportService->generateExcelExport($operationId, $operationEntiteId);
|
|
|
|
// Générer l'export JSON
|
|
$jsonFile = $exportService->generateJsonExport($operationId, $operationEntiteId, 'auto');
|
|
|
|
LogService::log('Export complet automatique créé avant suppression', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'operationId' => $operationId,
|
|
'operationActive' => $operationActive,
|
|
'excelFile' => $excelFile['filename'],
|
|
'jsonFile' => $jsonFile['filename']
|
|
]);
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de l\'export complet automatique avant suppression', [
|
|
'level' => 'warning',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $operationId,
|
|
'operationActive' => $operationActive
|
|
]);
|
|
// On continue même si l'export échoue
|
|
}
|
|
|
|
// Commencer une transaction pour supprimer toutes les données liées
|
|
$this->db->beginTransaction();
|
|
|
|
try {
|
|
// 1. Supprimer les médias liés à l'opération
|
|
$stmt = $this->db->prepare('DELETE FROM medias WHERE support = "operation" AND support_id = ?');
|
|
$stmt->execute([$operationId]);
|
|
$deletedMedias = $stmt->rowCount();
|
|
|
|
// 2. Supprimer l'historique des passages (via les passages de l'opération)
|
|
$stmt = $this->db->prepare('
|
|
DELETE oph FROM ope_pass_histo oph
|
|
INNER JOIN ope_pass op ON oph.fk_pass = op.id
|
|
WHERE op.fk_operation = ?
|
|
');
|
|
$stmt->execute([$operationId]);
|
|
$deletedPassHisto = $stmt->rowCount();
|
|
|
|
// 3. Supprimer les passages
|
|
$stmt = $this->db->prepare('DELETE FROM ope_pass WHERE fk_operation = ?');
|
|
$stmt->execute([$operationId]);
|
|
$deletedPass = $stmt->rowCount();
|
|
|
|
// 4. Supprimer les relations utilisateurs-secteurs
|
|
$stmt = $this->db->prepare('DELETE FROM ope_users_sectors WHERE fk_operation = ?');
|
|
$stmt->execute([$operationId]);
|
|
$deletedUsersSectors = $stmt->rowCount();
|
|
|
|
// 5. Supprimer les adresses des secteurs (via les secteurs de l'opération)
|
|
$stmt = $this->db->prepare('
|
|
DELETE sa FROM sectors_adresses sa
|
|
INNER JOIN ope_sectors os ON sa.fk_sector = os.id
|
|
WHERE os.fk_operation = ?
|
|
');
|
|
$stmt->execute([$operationId]);
|
|
$deletedSectorsAdresses = $stmt->rowCount();
|
|
|
|
// 6. Supprimer les secteurs
|
|
$stmt = $this->db->prepare('DELETE FROM ope_sectors WHERE fk_operation = ?');
|
|
$stmt->execute([$operationId]);
|
|
$deletedSectors = $stmt->rowCount();
|
|
|
|
// 7. Supprimer les utilisateurs de l'opération
|
|
$stmt = $this->db->prepare('DELETE FROM ope_users WHERE fk_operation = ?');
|
|
$stmt->execute([$operationId]);
|
|
$deletedUsers = $stmt->rowCount();
|
|
|
|
// 8. Supprimer l'opération elle-même
|
|
$stmt = $this->db->prepare('DELETE FROM operations WHERE id = ?');
|
|
$stmt->execute([$operationId]);
|
|
|
|
// Valider la transaction
|
|
$this->db->commit();
|
|
|
|
LogService::log('Suppression complète d\'une opération et de toutes ses données', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'userRole' => $userRole,
|
|
'userEntiteId' => $userEntiteId,
|
|
'operationEntiteId' => $operationEntiteId,
|
|
'operationId' => $operationId,
|
|
'operationActive' => $operationActive,
|
|
'deletedCounts' => [
|
|
'medias' => $deletedMedias,
|
|
'ope_pass_histo' => $deletedPassHisto,
|
|
'ope_pass' => $deletedPass,
|
|
'ope_users_sectors' => $deletedUsersSectors,
|
|
'sectors_adresses' => $deletedSectorsAdresses,
|
|
'ope_sectors' => $deletedSectors,
|
|
'ope_users' => $deletedUsers,
|
|
'operations' => 1
|
|
]
|
|
]);
|
|
|
|
// Préparer la réponse selon le statut de l'opération supprimée
|
|
$response = [
|
|
'status' => 'success',
|
|
'message' => 'Opération et toutes ses données supprimées avec succès',
|
|
'operation_was_active' => $operationActive,
|
|
'deleted_counts' => [
|
|
'medias' => $deletedMedias,
|
|
'passages_history' => $deletedPassHisto,
|
|
'passages' => $deletedPass,
|
|
'user_sectors' => $deletedUsersSectors,
|
|
'sectors_addresses' => $deletedSectorsAdresses,
|
|
'sectors' => $deletedSectors,
|
|
'users' => $deletedUsers
|
|
]
|
|
];
|
|
|
|
// Si l'opération supprimée était active, activer la dernière opération créée
|
|
$newActiveOperationId = null;
|
|
if ($operationActive) {
|
|
// Trouver la dernière opération créée de cette entité
|
|
$stmt = $this->db->prepare('
|
|
SELECT id FROM operations
|
|
WHERE fk_entite = ?
|
|
ORDER BY id DESC
|
|
LIMIT 1
|
|
');
|
|
$stmt->execute([$operationEntiteId]);
|
|
$lastOperation = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if ($lastOperation) {
|
|
$newActiveOperationId = (int)$lastOperation['id'];
|
|
|
|
// Activer cette opération
|
|
$stmt = $this->db->prepare('
|
|
UPDATE operations
|
|
SET chk_active = 1, updated_at = NOW(), fk_user_modif = ?
|
|
WHERE id = ?
|
|
');
|
|
$stmt->execute([$userId, $newActiveOperationId]);
|
|
|
|
LogService::log('Activation automatique de la dernière opération après suppression', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'entiteId' => $operationEntiteId,
|
|
'newActiveOperationId' => $newActiveOperationId,
|
|
'deletedOperationId' => $operationId
|
|
]);
|
|
}
|
|
}
|
|
|
|
// Récupérer les 3 dernières opérations (dont l'active)
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, libelle, date_deb, date_fin, chk_distinct_sectors,
|
|
created_at, updated_at, chk_active
|
|
FROM operations
|
|
WHERE fk_entite = ?
|
|
ORDER BY chk_active DESC, created_at DESC
|
|
LIMIT 3
|
|
');
|
|
$stmt->execute([$operationEntiteId]);
|
|
$operations = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$response['operations'] = $operations;
|
|
|
|
// Si une opération a été activée, récupérer ses données complètes
|
|
if ($newActiveOperationId) {
|
|
// Récupérer les secteurs de la nouvelle opération active
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, libelle, color, sector, created_at, updated_at, chk_active
|
|
FROM ope_sectors
|
|
WHERE fk_operation = ? AND chk_active = 1
|
|
ORDER BY libelle
|
|
');
|
|
$stmt->execute([$newActiveOperationId]);
|
|
$sectors = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Récupérer les passages de la nouvelle opération active
|
|
$stmt = $this->db->prepare('
|
|
SELECT
|
|
p.id, p.fk_operation, p.fk_sector, p.fk_user, p.fk_adresse,
|
|
p.passed_at, p.fk_type, p.numero, p.rue, p.rue_bis, p.ville,
|
|
p.fk_habitat, p.appt, p.niveau, p.residence, p.gps_lat, p.gps_lng,
|
|
p.encrypted_name, p.montant, p.fk_type_reglement, p.remarque,
|
|
p.encrypted_email, p.encrypted_phone, p.nom_recu, p.date_recu,
|
|
p.chk_email_sent, p.docremis, p.date_repasser, p.nb_passages,
|
|
p.chk_mobile, p.anomalie, p.created_at, p.updated_at, p.chk_active
|
|
FROM ope_pass p
|
|
WHERE p.fk_operation = ? AND p.chk_active = 1
|
|
ORDER BY p.created_at DESC
|
|
LIMIT 50
|
|
');
|
|
$stmt->execute([$newActiveOperationId]);
|
|
$passages = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Déchiffrer les données sensibles des passages
|
|
foreach ($passages as &$passage) {
|
|
$passage['name'] = ApiService::decryptData($passage['encrypted_name']);
|
|
$passage['email'] = !empty($passage['encrypted_email']) ?
|
|
ApiService::decryptSearchableData($passage['encrypted_email']) : '';
|
|
$passage['phone'] = !empty($passage['encrypted_phone']) ?
|
|
ApiService::decryptData($passage['encrypted_phone']) : '';
|
|
|
|
// Suppression des champs chiffrés
|
|
unset($passage['encrypted_name'], $passage['encrypted_email'], $passage['encrypted_phone']);
|
|
}
|
|
|
|
// Récupérer les relations utilisateurs-secteurs
|
|
$stmt = $this->db->prepare('
|
|
SELECT
|
|
ous.id, ous.fk_operation, ous.fk_user, ous.fk_sector,
|
|
ous.created_at, ous.updated_at, ous.chk_active,
|
|
u.encrypted_name as user_name, u.first_name as user_first_name,
|
|
s.libelle as sector_name
|
|
FROM ope_users_sectors ous
|
|
INNER JOIN users u ON u.id = ous.fk_user
|
|
INNER JOIN ope_sectors s ON s.id = ous.fk_sector
|
|
WHERE ous.fk_operation = ? AND ous.chk_active = 1
|
|
ORDER BY s.libelle, u.encrypted_name
|
|
');
|
|
$stmt->execute([$newActiveOperationId]);
|
|
$usersSectors = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Déchiffrer les noms d'utilisateurs
|
|
foreach ($usersSectors as &$userSector) {
|
|
$userSector['user_name'] = ApiService::decryptData($userSector['user_name']);
|
|
unset($userSector['encrypted_name']);
|
|
}
|
|
|
|
$response['activated_operation'] = [
|
|
'id' => $newActiveOperationId,
|
|
'sectors' => $sectors,
|
|
'passages' => $passages,
|
|
'users_sectors' => $usersSectors
|
|
];
|
|
}
|
|
|
|
Response::json($response, 200);
|
|
} catch (Exception $e) {
|
|
// Annuler la transaction en cas d'erreur
|
|
$this->db->rollBack();
|
|
|
|
LogService::log('Erreur lors de la suppression complète de l\'opération', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $operationId,
|
|
'operationActive' => $operationActive,
|
|
'userId' => $userId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la suppression complète de l\'opération'
|
|
], 500);
|
|
return;
|
|
}
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de la suppression de l\'opération', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $id,
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la suppression de l\'opération'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export Excel d'une opération (retourne directement le fichier)
|
|
*/
|
|
public function exportExcel(string $id): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$entiteId = $this->getUserEntiteId($userId);
|
|
if (!$entiteId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Entité non trouvée pour cet utilisateur'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$operationId = (int)$id;
|
|
|
|
// Vérifier d'abord si l'opération existe (sans filtrer par entité)
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, libelle, chk_active, fk_entite
|
|
FROM operations
|
|
WHERE id = ?
|
|
');
|
|
|
|
$stmt->execute([$operationId]);
|
|
$operation = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$operation) {
|
|
LogService::log('Opération inexistante pour export Excel', [
|
|
'level' => 'warning',
|
|
'operationId' => $operationId,
|
|
'userId' => $userId,
|
|
'entiteId' => $entiteId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Opération non trouvée'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
// Vérifier que l'opération appartient à l'entité de l'utilisateur
|
|
if ((int)$operation['fk_entite'] !== $entiteId) {
|
|
LogService::log('Tentative d\'accès à une opération d\'une autre entité pour export Excel', [
|
|
'level' => 'warning',
|
|
'operationId' => $operationId,
|
|
'operationEntiteId' => (int)$operation['fk_entite'],
|
|
'userEntiteId' => $entiteId,
|
|
'userId' => $userId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous n\'avez pas accès à cette opération'
|
|
], 403);
|
|
return;
|
|
}
|
|
|
|
// Paramètre optionnel pour filtrer par utilisateur
|
|
$filterUserId = isset($_GET['user_id']) ? (int)$_GET['user_id'] : null;
|
|
|
|
// Générer l'export Excel
|
|
$exportService = new ExportService();
|
|
$fileInfo = $exportService->generateExcelExport($operationId, $entiteId, $filterUserId);
|
|
|
|
// Construire le chemin complet du fichier
|
|
$filepath = getcwd() . '/' . $fileInfo['path'];
|
|
|
|
if (!file_exists($filepath)) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Fichier Excel non trouvé'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
// Nettoyer le nom de l'opération pour le nom de fichier
|
|
$operationName = preg_replace('/[^a-zA-Z0-9\-_]/', '_', $operation['libelle']);
|
|
$userSuffix = $filterUserId ? "-user{$filterUserId}" : '';
|
|
$timestamp = date('Ymd-His');
|
|
$downloadFilename = "export-{$operationName}{$userSuffix}-{$timestamp}.xlsx";
|
|
|
|
// Envoyer le fichier Excel directement
|
|
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
|
|
header('Content-Disposition: attachment; filename="' . $downloadFilename . '"');
|
|
header('Content-Length: ' . filesize($filepath));
|
|
header('Cache-Control: must-revalidate');
|
|
header('Pragma: public');
|
|
|
|
// Lire et envoyer le fichier
|
|
readfile($filepath);
|
|
|
|
LogService::log('Export Excel téléchargé', [
|
|
'level' => 'info',
|
|
'operationId' => $operationId,
|
|
'entiteId' => $entiteId,
|
|
'userId' => $userId,
|
|
'filename' => $downloadFilename,
|
|
'filterUserId' => $filterUserId
|
|
]);
|
|
|
|
exit;
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de l\'export Excel', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $id,
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la génération de l\'export Excel'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export JSON d'une opération (sauvegarde)
|
|
*/
|
|
public function exportJson(string $id): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$entiteId = $this->getUserEntiteId($userId);
|
|
if (!$entiteId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Entité non trouvée pour cet utilisateur'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$operationId = (int)$id;
|
|
|
|
// Vérifier que l'opération existe et appartient à l'entité
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, chk_active
|
|
FROM operations
|
|
WHERE id = ? AND fk_entite = ?
|
|
');
|
|
|
|
$stmt->execute([$operationId, $entiteId]);
|
|
$operation = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$operation) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Opération non trouvée'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
// Type d'export (manual par défaut)
|
|
$exportType = $_GET['type'] ?? 'manual';
|
|
|
|
// Générer l'export JSON
|
|
$exportService = new ExportService();
|
|
$fileInfo = $exportService->generateJsonExport($operationId, $entiteId, $exportType);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'message' => 'Sauvegarde JSON générée avec succès',
|
|
'file' => $fileInfo
|
|
], 200);
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de l\'export JSON', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $id,
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la génération de la sauvegarde JSON'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Export complet (Excel + JSON)
|
|
*/
|
|
public function exportFull(string $id): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$entiteId = $this->getUserEntiteId($userId);
|
|
if (!$entiteId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Entité non trouvée pour cet utilisateur'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$operationId = (int)$id;
|
|
|
|
// Vérifier que l'opération existe et appartient à l'entité
|
|
$stmt = $this->db->prepare('
|
|
SELECT id, chk_active
|
|
FROM operations
|
|
WHERE id = ? AND fk_entite = ?
|
|
');
|
|
|
|
$stmt->execute([$operationId, $entiteId]);
|
|
$operation = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$operation) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Opération non trouvée'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$exportService = new ExportService();
|
|
|
|
// Générer les deux exports
|
|
$excelFile = $exportService->generateExcelExport($operationId, $entiteId);
|
|
$jsonFile = $exportService->generateJsonExport($operationId, $entiteId, 'manual');
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'message' => 'Export complet généré avec succès',
|
|
'files' => [
|
|
'excel' => $excelFile,
|
|
'json' => $jsonFile
|
|
]
|
|
], 200);
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de l\'export complet', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $id,
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la génération de l\'export complet'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Liste des sauvegardes d'une opération
|
|
*/
|
|
public function getBackups(string $id): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$entiteId = $this->getUserEntiteId($userId);
|
|
if (!$entiteId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Entité non trouvée pour cet utilisateur'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$operationId = (int)$id;
|
|
|
|
// Vérifier que l'opération existe et appartient à l'entité
|
|
$stmt = $this->db->prepare('
|
|
SELECT id
|
|
FROM operations
|
|
WHERE id = ? AND fk_entite = ?
|
|
');
|
|
|
|
$stmt->execute([$operationId, $entiteId]);
|
|
$operation = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$operation) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Opération non trouvée'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
// Récupérer les fichiers d'export de cette opération
|
|
$stmt = $this->db->prepare('
|
|
SELECT
|
|
id, fichier, file_type, file_size, description,
|
|
created_at, fk_user_creat
|
|
FROM medias
|
|
WHERE support = "operation" AND support_id = ? AND fk_entite = ?
|
|
ORDER BY created_at DESC
|
|
');
|
|
|
|
$stmt->execute([$operationId, $entiteId]);
|
|
$backups = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'backups' => $backups
|
|
], 200);
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de la récupération des sauvegardes', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $id,
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la récupération des sauvegardes'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Télécharger une sauvegarde spécifique
|
|
*/
|
|
public function downloadBackup(string $id, string $backup_id): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$entiteId = $this->getUserEntiteId($userId);
|
|
if (!$entiteId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Entité non trouvée pour cet utilisateur'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$operationId = (int)$id;
|
|
$backupId = (int)$backup_id;
|
|
|
|
// Vérifier que le fichier existe et appartient à l'opération/entité
|
|
$stmt = $this->db->prepare('
|
|
SELECT m.fichier, m.file_path, m.mime_type, m.original_name
|
|
FROM medias m
|
|
INNER JOIN operations o ON o.id = m.support_id
|
|
WHERE m.id = ? AND m.support = "operation" AND m.support_id = ?
|
|
AND o.fk_entite = ?
|
|
');
|
|
|
|
$stmt->execute([$backupId, $operationId, $entiteId]);
|
|
$backup = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$backup) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Fichier de sauvegarde non trouvé'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$filepath = getcwd() . '/' . $backup['file_path'];
|
|
|
|
if (!file_exists($filepath)) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Fichier physique non trouvé'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
// Envoyer le fichier
|
|
header('Content-Type: ' . $backup['mime_type']);
|
|
header('Content-Disposition: attachment; filename="' . $backup['original_name'] . '"');
|
|
header('Content-Length: ' . filesize($filepath));
|
|
readfile($filepath);
|
|
exit;
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors du téléchargement de sauvegarde', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $id,
|
|
'backupId' => $backup_id,
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors du téléchargement'
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Supprimer une sauvegarde
|
|
*/
|
|
public function deleteBackup(string $id, string $backup_id): void {
|
|
try {
|
|
$userId = Session::getUserId();
|
|
if (!$userId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Vous devez être connecté pour effectuer cette action'
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$entiteId = $this->getUserEntiteId($userId);
|
|
if (!$entiteId) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Entité non trouvée pour cet utilisateur'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
$operationId = (int)$id;
|
|
$backupId = (int)$backup_id;
|
|
|
|
// Vérifier que le fichier existe et appartient à l'opération/entité
|
|
$stmt = $this->db->prepare('
|
|
SELECT m.id, m.file_path
|
|
FROM medias m
|
|
INNER JOIN operations o ON o.id = m.support_id
|
|
WHERE m.id = ? AND m.support = "operation" AND m.support_id = ?
|
|
AND o.fk_entite = ?
|
|
');
|
|
|
|
$stmt->execute([$backupId, $operationId, $entiteId]);
|
|
$backup = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
|
|
if (!$backup) {
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Fichier de sauvegarde non trouvé'
|
|
], 404);
|
|
return;
|
|
}
|
|
|
|
// Supprimer le fichier physique
|
|
$filepath = getcwd() . '/' . $backup['file_path'];
|
|
if (file_exists($filepath)) {
|
|
unlink($filepath);
|
|
}
|
|
|
|
// Supprimer l'enregistrement en base
|
|
$stmt = $this->db->prepare('DELETE FROM medias WHERE id = ?');
|
|
$stmt->execute([$backupId]);
|
|
|
|
LogService::log('Suppression d\'une sauvegarde', [
|
|
'level' => 'info',
|
|
'userId' => $userId,
|
|
'operationId' => $operationId,
|
|
'backupId' => $backupId
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'success',
|
|
'message' => 'Sauvegarde supprimée avec succès'
|
|
], 200);
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de la suppression de sauvegarde', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $id,
|
|
'backupId' => $backup_id,
|
|
'userId' => $userId ?? null
|
|
]);
|
|
|
|
Response::json([
|
|
'status' => 'error',
|
|
'message' => 'Erreur lors de la suppression'
|
|
], 500);
|
|
}
|
|
}
|
|
}
|