934 lines
32 KiB
PHP
934 lines
32 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../vendor/autoload.php';
|
|
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
use PhpOffice\PhpSpreadsheet\Writer\Xls;
|
|
use PhpOffice\PhpSpreadsheet\Writer\Csv;
|
|
|
|
require_once __DIR__ . '/../Services/FileService.php';
|
|
|
|
|
|
class ExportService {
|
|
private \PDO $db;
|
|
private FileService $fileService;
|
|
|
|
public function __construct() {
|
|
$this->db = Database::getInstance();
|
|
$this->fileService = new FileService();
|
|
}
|
|
|
|
/**
|
|
* Génère un export Excel complet d'une opération
|
|
*
|
|
* @param int $operationId ID de l'opération
|
|
* @param int $entiteId ID de l'entité
|
|
* @param int|null $userId Filtrer par utilisateur (optionnel)
|
|
* @return array Informations du fichier généré
|
|
*/
|
|
public function generateExcelExport(int $operationId, int $entiteId, ?int $userId = null): array {
|
|
try {
|
|
// Récupérer les données de l'opération
|
|
$operationData = $this->getOperationData($operationId, $entiteId);
|
|
if (!$operationData) {
|
|
throw new Exception('Opération non trouvée');
|
|
}
|
|
|
|
// Créer le dossier de destination
|
|
$exportDir = $this->fileService->createDirectory($entiteId, "/{$entiteId}/operations/{$operationId}/exports/excel");
|
|
|
|
LogService::log('exportDir', [
|
|
'level' => 'warning',
|
|
'exportDir' => $exportDir,
|
|
]);
|
|
|
|
// Générer le nom du fichier
|
|
$timestamp = date('Ymd-His');
|
|
$userSuffix = $userId ? "-user{$userId}" : '';
|
|
$filename = "geosector-export-{$operationId}{$userSuffix}-{$timestamp}.xlsx";
|
|
$filepath = $exportDir . '/' . $filename;
|
|
|
|
// Créer le spreadsheet
|
|
$spreadsheet = new PhpOffice\PhpSpreadsheet\Spreadsheet();
|
|
|
|
// Insérer les données
|
|
$this->createPassagesSheet($spreadsheet, $operationId, $userId);
|
|
$this->createUsersSheet($spreadsheet, $operationId);
|
|
$this->createSectorsSheet($spreadsheet, $operationId);
|
|
$this->createUserSectorsSheet($spreadsheet, $operationId);
|
|
|
|
// Supprimer la feuille par défaut (Worksheet) qui est créée automatiquement
|
|
$defaultSheet = $spreadsheet->getSheetByName('Worksheet');
|
|
if ($defaultSheet) {
|
|
$spreadsheet->removeSheetByIndex($spreadsheet->getIndex($defaultSheet));
|
|
}
|
|
|
|
// Essayer d'abord le writer XLSX, sinon utiliser CSV
|
|
try {
|
|
$writer = new Xls($spreadsheet);
|
|
$writer->save($filepath);
|
|
} catch (Exception $e) {
|
|
// Si XLSX échoue, utiliser CSV comme fallback
|
|
$csvPath = str_replace('.xlsx', '.csv', $filepath);
|
|
$csvWriter = new Csv($spreadsheet);
|
|
$csvWriter->setDelimiter(';');
|
|
$csvWriter->setEnclosure('"');
|
|
$csvWriter->save($csvPath);
|
|
|
|
// Mettre à jour les variables pour le CSV
|
|
$filepath = $csvPath;
|
|
$filename = str_replace('.xlsx', '.csv', $filename);
|
|
|
|
LogService::log('Fallback vers CSV car XLSX a échoué', [
|
|
'level' => 'warning',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $operationId
|
|
]);
|
|
}
|
|
|
|
// Appliquer les permissions sur le fichier
|
|
$this->fileService->setFilePermissions($filepath);
|
|
|
|
// Déterminer le type de fichier réellement généré
|
|
$fileType = str_ends_with($filename, '.csv') ? 'csv' : 'xlsx';
|
|
|
|
// Enregistrer en base de données
|
|
$mediaId = $this->fileService->saveToMediasTable($entiteId, $operationId, $filename, $filepath, $fileType, 'Export Excel opération - ' . $operationData['libelle']);
|
|
|
|
LogService::log('Export Excel généré', [
|
|
'level' => 'info',
|
|
'operationId' => $operationId,
|
|
'entiteId' => $entiteId,
|
|
'path' => $exportDir,
|
|
'filename' => $filename,
|
|
'mediaId' => $mediaId
|
|
]);
|
|
|
|
return [
|
|
'id' => $mediaId,
|
|
'filename' => $filename,
|
|
'path' => str_replace(getcwd() . '/', '', $filepath),
|
|
'size' => filesize($filepath),
|
|
'type' => 'excel'
|
|
];
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de la génération de l\'export Excel', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $operationId,
|
|
'entiteId' => $entiteId
|
|
]);
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Génère un export JSON complet d'une opération (chiffré et compressé)
|
|
*
|
|
* @param int $operationId ID de l'opération
|
|
* @param int $entiteId ID de l'entité
|
|
* @param string $type Type d'export (auto, manual)
|
|
* @return array Informations du fichier généré
|
|
*/
|
|
public function generateJsonExport(int $operationId, int $entiteId, string $type = 'manual'): array {
|
|
try {
|
|
// Récupérer toutes les données de l'opération
|
|
$exportData = $this->collectOperationData($operationId, $entiteId);
|
|
|
|
// Créer le dossier de destination
|
|
$exportDir = $this->fileService->createDirectory($entiteId, "/{$entiteId}/operations/{$operationId}/exports/json");
|
|
|
|
// Initialiser le service de chiffrement
|
|
$backupService = new BackupEncryptionService();
|
|
|
|
// Générer le JSON original
|
|
$jsonData = json_encode($exportData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
|
|
|
// Chiffrer et compresser les données
|
|
$encryptedData = $backupService->encryptBackup($jsonData);
|
|
|
|
// Générer le nom du fichier avec extension appropriée
|
|
$timestamp = date('Ymd-His');
|
|
$filename = $backupService->generateBackupFilename($operationId, $timestamp, $type);
|
|
$filepath = $exportDir . '/' . $filename;
|
|
|
|
// Sauvegarder le fichier chiffré
|
|
file_put_contents($filepath, $encryptedData);
|
|
|
|
// Appliquer les permissions sur le fichier
|
|
$this->fileService->setFilePermissions($filepath);
|
|
|
|
// Obtenir les statistiques de compression
|
|
$stats = $backupService->getCompressionStats($jsonData, $encryptedData);
|
|
|
|
// Enregistrer en base de données avec le bon type MIME
|
|
$mediaId = $this->fileService->saveToMediasTable(
|
|
$entiteId,
|
|
$operationId,
|
|
$filename,
|
|
$filepath,
|
|
'enc',
|
|
"Sauvegarde chiffrée opération - {$type} - " . $exportData['operation']['libelle'],
|
|
'backup'
|
|
);
|
|
|
|
LogService::log('Export JSON chiffré généré', [
|
|
'level' => 'info',
|
|
'operationId' => $operationId,
|
|
'entiteId' => $entiteId,
|
|
'filename' => $filename,
|
|
'type' => $type,
|
|
'mediaId' => $mediaId,
|
|
'original_size' => $stats['original_size'],
|
|
'final_size' => $stats['final_size'],
|
|
'compression_ratio' => $stats['compression_ratio'] . '%',
|
|
'is_compressed' => $stats['is_compressed'],
|
|
'cipher' => $stats['cipher']
|
|
]);
|
|
|
|
return [
|
|
'id' => $mediaId,
|
|
'filename' => $filename,
|
|
'path' => str_replace(getcwd() . '/', '', $filepath),
|
|
'size' => filesize($filepath),
|
|
'type' => 'encrypted_json',
|
|
'compression_stats' => $stats
|
|
];
|
|
} catch (Exception $e) {
|
|
LogService::log('Erreur lors de la génération de l\'export JSON chiffré', [
|
|
'level' => 'error',
|
|
'error' => $e->getMessage(),
|
|
'operationId' => $operationId,
|
|
'entiteId' => $entiteId
|
|
]);
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Crée la feuille des passages
|
|
*/
|
|
private function createPassagesSheet(Spreadsheet $spreadsheet, int $operationId, ?int $userId = null): void {
|
|
$sheet = $spreadsheet->createSheet();
|
|
$sheet->setTitle('Passages');
|
|
|
|
// En-têtes
|
|
$headers = [
|
|
'ID_Passage',
|
|
'Date',
|
|
'Heure',
|
|
'Prénom',
|
|
'Nom',
|
|
'Tournée',
|
|
'Type',
|
|
'N°',
|
|
'Bis',
|
|
'Rue',
|
|
'Ville',
|
|
'Habitat',
|
|
'Donateur',
|
|
'Email',
|
|
'Tél',
|
|
'Montant',
|
|
'Règlement',
|
|
'Remarque',
|
|
'FK_User',
|
|
'FK_Sector',
|
|
'FK_Operation'
|
|
];
|
|
|
|
// Écrire les en-têtes
|
|
$sheet->fromArray([$headers], null, 'A1');
|
|
|
|
// Récupérer les données des passages
|
|
$sql = '
|
|
SELECT
|
|
p.id, p.passed_at, p.fk_type, p.numero, p.rue_bis, p.rue, p.ville,
|
|
p.fk_habitat, p.appt, p.niveau, p.encrypted_name, p.encrypted_email,
|
|
p.encrypted_phone, p.montant, p.fk_type_reglement, p.remarque,
|
|
p.fk_user, p.fk_sector, p.fk_operation,
|
|
u.encrypted_name as user_name, u.first_name as user_first_name, u.sect_name,
|
|
xtr.libelle as reglement_libelle
|
|
FROM ope_pass p
|
|
LEFT JOIN users u ON u.id = p.fk_user
|
|
LEFT JOIN x_types_reglements xtr ON xtr.id = p.fk_type_reglement
|
|
WHERE p.fk_operation = ? AND p.chk_active = 1
|
|
';
|
|
|
|
$params = [$operationId];
|
|
if ($userId) {
|
|
$sql .= ' AND p.fk_user = ?';
|
|
$params[] = $userId;
|
|
}
|
|
|
|
$sql .= ' ORDER BY p.passed_at DESC';
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute($params);
|
|
$passages = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Remplir les données
|
|
$row = 2;
|
|
foreach ($passages as $passage) {
|
|
$dateEve = $passage['passed_at'] ? date('d/m/Y', strtotime($passage['passed_at'])) : '';
|
|
$heureEve = $passage['passed_at'] ? date('H:i', strtotime($passage['passed_at'])) : '';
|
|
|
|
// Déchiffrer les données
|
|
$donateur = ApiService::decryptData($passage['encrypted_name']);
|
|
$email = !empty($passage['encrypted_email']) ? ApiService::decryptSearchableData($passage['encrypted_email']) : '';
|
|
$phone = !empty($passage['encrypted_phone']) ? ApiService::decryptData($passage['encrypted_phone']) : '';
|
|
$userName = ApiService::decryptData($passage['user_name']);
|
|
|
|
// Type de passage
|
|
$typeLabels = [
|
|
1 => 'Effectué',
|
|
2 => 'A finaliser',
|
|
3 => 'Refusé',
|
|
4 => 'Don',
|
|
9 => 'Habitat vide'
|
|
];
|
|
$typeLabel = $typeLabels[$passage['fk_type']] ?? $passage['fk_type'];
|
|
|
|
// Habitat
|
|
$habitat = $passage['fk_habitat'] == 1 ? 'Individuel' :
|
|
"Etage {$passage['niveau']} - Appt {$passage['appt']}";
|
|
|
|
$rowData = [
|
|
$passage['id'],
|
|
$dateEve,
|
|
$heureEve,
|
|
$passage['user_first_name'],
|
|
$userName,
|
|
$passage['sect_name'],
|
|
$typeLabel,
|
|
$passage['numero'],
|
|
$passage['rue_bis'],
|
|
$passage['rue'],
|
|
$passage['ville'],
|
|
$habitat,
|
|
$donateur,
|
|
$email,
|
|
$phone,
|
|
$passage['montant'],
|
|
$passage['reglement_libelle'],
|
|
$passage['remarque'],
|
|
$passage['fk_user'],
|
|
$passage['fk_sector'],
|
|
$passage['fk_operation']
|
|
];
|
|
|
|
$sheet->fromArray([$rowData], null, "A{$row}");
|
|
$row++;
|
|
}
|
|
|
|
// Auto-ajuster les colonnes
|
|
foreach (range('A', 'T') as $col) {
|
|
$sheet->getColumnDimension($col)->setAutoSize(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Crée la feuille des utilisateurs
|
|
*/
|
|
private function createUsersSheet(Spreadsheet $spreadsheet, int $operationId): void {
|
|
$sheet = $spreadsheet->createSheet();
|
|
$sheet->setTitle('Utilisateurs');
|
|
|
|
// En-têtes
|
|
$headers = [
|
|
'ID_User',
|
|
'Nom',
|
|
'Prénom',
|
|
'Email',
|
|
'Téléphone',
|
|
'Mobile',
|
|
'Rôle',
|
|
'Date_création',
|
|
'Actif',
|
|
'FK_Entite'
|
|
];
|
|
|
|
$sheet->fromArray([$headers], null, 'A1');
|
|
|
|
// Récupérer les utilisateurs de l'opération
|
|
$sql = '
|
|
SELECT DISTINCT
|
|
u.id, u.encrypted_name, u.first_name, u.encrypted_email,
|
|
u.encrypted_phone, u.encrypted_mobile, u.fk_role, u.created_at,
|
|
u.chk_active, u.fk_entite,
|
|
r.libelle as role_libelle
|
|
FROM users u
|
|
INNER JOIN ope_users ou ON ou.fk_user = u.id
|
|
LEFT JOIN x_users_roles r ON r.id = u.fk_role
|
|
WHERE ou.fk_operation = ? AND ou.chk_active = 1
|
|
ORDER BY u.encrypted_name
|
|
';
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([$operationId]);
|
|
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$row = 2;
|
|
foreach ($users as $user) {
|
|
$rowData = [
|
|
$user['id'],
|
|
ApiService::decryptData($user['encrypted_name']),
|
|
$user['first_name'],
|
|
!empty($user['encrypted_email']) ? ApiService::decryptSearchableData($user['encrypted_email']) : '',
|
|
!empty($user['encrypted_phone']) ? ApiService::decryptData($user['encrypted_phone']) : '',
|
|
!empty($user['encrypted_mobile']) ? ApiService::decryptData($user['encrypted_mobile']) : '',
|
|
$user['role_libelle'],
|
|
date('d/m/Y H:i', strtotime($user['created_at'])),
|
|
$user['chk_active'] ? 'Oui' : 'Non',
|
|
$user['fk_entite']
|
|
];
|
|
|
|
$sheet->fromArray([$rowData], null, "A{$row}");
|
|
$row++;
|
|
}
|
|
|
|
foreach (range('A', 'J') as $col) {
|
|
$sheet->getColumnDimension($col)->setAutoSize(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Crée la feuille des secteurs
|
|
*/
|
|
private function createSectorsSheet(Spreadsheet $spreadsheet, int $operationId): void {
|
|
$sheet = $spreadsheet->createSheet();
|
|
$sheet->setTitle('Secteurs');
|
|
|
|
$headers = ['ID_Sector', 'Libellé', 'Couleur', 'Date_création', 'Actif', 'FK_Operation'];
|
|
$sheet->fromArray([$headers], null, 'A1');
|
|
|
|
$sql = '
|
|
SELECT id, libelle, color, created_at, chk_active, fk_operation
|
|
FROM ope_sectors
|
|
WHERE fk_operation = ? AND chk_active = 1
|
|
ORDER BY libelle
|
|
';
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([$operationId]);
|
|
$sectors = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$row = 2;
|
|
foreach ($sectors as $sector) {
|
|
$rowData = [
|
|
$sector['id'],
|
|
$sector['libelle'],
|
|
$sector['color'],
|
|
date('d/m/Y H:i', strtotime($sector['created_at'])),
|
|
$sector['chk_active'] ? 'Oui' : 'Non',
|
|
$sector['fk_operation']
|
|
];
|
|
|
|
$sheet->fromArray([$rowData], null, "A{$row}");
|
|
$row++;
|
|
}
|
|
|
|
foreach (range('A', 'F') as $col) {
|
|
$sheet->getColumnDimension($col)->setAutoSize(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Crée la feuille des relations secteurs-utilisateurs
|
|
*/
|
|
private function createUserSectorsSheet(Spreadsheet $spreadsheet, int $operationId): void {
|
|
$sheet = $spreadsheet->createSheet();
|
|
$sheet->setTitle('Secteurs-Utilisateurs');
|
|
|
|
$headers = [
|
|
'ID_Relation',
|
|
'FK_Sector',
|
|
'Nom_Secteur',
|
|
'FK_User',
|
|
'Nom_Utilisateur',
|
|
'Date_assignation',
|
|
'FK_Operation'
|
|
];
|
|
$sheet->fromArray([$headers], null, 'A1');
|
|
|
|
$sql = '
|
|
SELECT
|
|
ous.id, ous.fk_sector, ous.fk_user, ous.created_at, ous.fk_operation,
|
|
s.libelle as sector_name,
|
|
u.encrypted_name as user_name, u.first_name
|
|
FROM ope_users_sectors ous
|
|
INNER JOIN ope_sectors s ON s.id = ous.fk_sector
|
|
INNER JOIN users u ON u.id = ous.fk_user
|
|
WHERE ous.fk_operation = ? AND ous.chk_active = 1
|
|
ORDER BY s.libelle, u.encrypted_name
|
|
';
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute([$operationId]);
|
|
$userSectors = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
$row = 2;
|
|
foreach ($userSectors as $us) {
|
|
$userName = ApiService::decryptData($us['user_name']);
|
|
$fullUserName = $us['first_name'] ? $us['first_name'] . ' ' . $userName : $userName;
|
|
|
|
$rowData = [
|
|
$us['id'],
|
|
$us['fk_sector'],
|
|
$us['sector_name'],
|
|
$us['fk_user'],
|
|
$fullUserName,
|
|
date('d/m/Y H:i', strtotime($us['created_at'])),
|
|
$us['fk_operation']
|
|
];
|
|
|
|
$sheet->fromArray([$rowData], null, "A{$row}");
|
|
$row++;
|
|
}
|
|
|
|
foreach (range('A', 'G') as $col) {
|
|
$sheet->getColumnDimension($col)->setAutoSize(true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Collecte toutes les données d'une opération pour l'export JSON
|
|
*/
|
|
private function collectOperationData(int $operationId, int $entiteId): array {
|
|
// Métadonnées de l'export
|
|
$exportData = [
|
|
'export_metadata' => [
|
|
'version' => '1.0',
|
|
'export_date' => date('c'),
|
|
'source_entite_id' => $entiteId,
|
|
'export_type' => 'full_operation'
|
|
]
|
|
];
|
|
|
|
// Données de l'opération
|
|
$exportData['operation'] = $this->getOperationData($operationId, $entiteId);
|
|
|
|
// Utilisateurs de l'opération
|
|
$exportData['users'] = $this->getOperationUsers($operationId);
|
|
|
|
// Secteurs de l'opération
|
|
$exportData['sectors'] = $this->getOperationSectors($operationId);
|
|
|
|
// Passages de l'opération
|
|
$exportData['passages'] = $this->getOperationPassages($operationId);
|
|
|
|
// Relations utilisateurs-secteurs
|
|
$exportData['user_sectors'] = $this->getOperationUserSectors($operationId);
|
|
|
|
return $exportData;
|
|
}
|
|
|
|
/**
|
|
* Récupère les données de l'opération
|
|
*/
|
|
private function getOperationData(int $operationId, int $entiteId): ?array {
|
|
$stmt = $this->db->prepare('
|
|
SELECT * FROM operations
|
|
WHERE id = ? AND fk_entite = ?
|
|
');
|
|
$stmt->execute([$operationId, $entiteId]);
|
|
return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
|
|
}
|
|
|
|
/**
|
|
* Récupère les utilisateurs de l'opération
|
|
*/
|
|
private function getOperationUsers(int $operationId): array {
|
|
$stmt = $this->db->prepare('
|
|
SELECT DISTINCT u.*
|
|
FROM users u
|
|
INNER JOIN ope_users ou ON ou.fk_user = u.id
|
|
WHERE ou.fk_operation = ? AND ou.chk_active = 1
|
|
');
|
|
$stmt->execute([$operationId]);
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Récupère les secteurs de l'opération
|
|
*/
|
|
private function getOperationSectors(int $operationId): array {
|
|
$stmt = $this->db->prepare('
|
|
SELECT * FROM ope_sectors
|
|
WHERE fk_operation = ? AND chk_active = 1
|
|
');
|
|
$stmt->execute([$operationId]);
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Récupère les passages de l'opération
|
|
*/
|
|
private function getOperationPassages(int $operationId): array {
|
|
$stmt = $this->db->prepare('
|
|
SELECT * FROM ope_pass
|
|
WHERE fk_operation = ? AND chk_active = 1
|
|
');
|
|
$stmt->execute([$operationId]);
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Récupère les relations utilisateurs-secteurs
|
|
*/
|
|
private function getOperationUserSectors(int $operationId): array {
|
|
$stmt = $this->db->prepare('
|
|
SELECT * FROM ope_users_sectors
|
|
WHERE fk_operation = ? AND chk_active = 1
|
|
');
|
|
$stmt->execute([$operationId]);
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
}
|
|
|
|
/**
|
|
* Récupère les données des passages dans un format simple pour l'export Excel
|
|
* (inspiré de l'ancienne version qui fonctionne)
|
|
*/
|
|
private function getSimplePassagesData(int $operationId, ?int $userId = null): array {
|
|
// En-têtes (comme dans l'ancienne version)
|
|
$aData = [];
|
|
$aData[] = [
|
|
'Date',
|
|
'Heure',
|
|
'Prenom',
|
|
'Nom',
|
|
'Tournee',
|
|
'Type',
|
|
'N°',
|
|
'Rue',
|
|
'Ville',
|
|
'Habitat',
|
|
'Donateur',
|
|
'Email',
|
|
'Tel',
|
|
'Montant',
|
|
'Reglement',
|
|
'Remarque'
|
|
];
|
|
|
|
// Récupérer les données des passages
|
|
$sql = '
|
|
SELECT
|
|
p.passed_at, p.fk_type, p.numero, p.rue_bis, p.rue, p.ville,
|
|
p.fk_habitat, p.appt, p.niveau, p.encrypted_name, p.encrypted_email,
|
|
p.encrypted_phone, p.montant, p.fk_type_reglement, p.remarque,
|
|
u.encrypted_name as user_name, u.first_name as user_first_name, u.sect_name,
|
|
xtr.libelle as reglement_libelle
|
|
FROM ope_pass p
|
|
LEFT JOIN users u ON u.id = p.fk_user
|
|
LEFT JOIN x_types_reglements xtr ON xtr.id = p.fk_type_reglement
|
|
WHERE p.fk_operation = ? AND p.chk_active = 1
|
|
';
|
|
|
|
$params = [$operationId];
|
|
if ($userId) {
|
|
$sql .= ' AND p.fk_user = ?';
|
|
$params[] = $userId;
|
|
}
|
|
|
|
$sql .= ' ORDER BY p.passed_at DESC';
|
|
|
|
$stmt = $this->db->prepare($sql);
|
|
$stmt->execute($params);
|
|
$passages = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// Traiter les données comme dans l'ancienne version
|
|
foreach ($passages as $p) {
|
|
// Type de passage
|
|
switch ($p["fk_type"]) {
|
|
case 1:
|
|
$ptype = "Effectué";
|
|
$preglement = $p["reglement_libelle"];
|
|
break;
|
|
case 2:
|
|
$ptype = "A finaliser";
|
|
$preglement = "";
|
|
break;
|
|
case 3:
|
|
$ptype = "Refusé";
|
|
$preglement = "";
|
|
break;
|
|
case 4:
|
|
$ptype = "Don";
|
|
$preglement = "";
|
|
break;
|
|
case 9:
|
|
$ptype = "Habitat vide";
|
|
$preglement = "";
|
|
break;
|
|
default:
|
|
$ptype = $p["fk_type"];
|
|
$preglement = "";
|
|
break;
|
|
}
|
|
|
|
// Habitat
|
|
if ($p["fk_habitat"] == 1) {
|
|
$phabitat = "Individuel";
|
|
} else {
|
|
$phabitat = "Etage " . $p["niveau"] . " - Appt " . $p["appt"];
|
|
}
|
|
|
|
// Dates
|
|
$dateEve = $p["passed_at"] ? date("d/m/Y", strtotime($p["passed_at"])) : "";
|
|
$heureEve = $p["passed_at"] ? date("H:i", strtotime($p["passed_at"])) : "";
|
|
|
|
// Déchiffrer les données
|
|
$donateur = ApiService::decryptData($p["encrypted_name"]);
|
|
$email = !empty($p["encrypted_email"]) ? ApiService::decryptSearchableData($p["encrypted_email"]) : "";
|
|
$phone = !empty($p["encrypted_phone"]) ? ApiService::decryptData($p["encrypted_phone"]) : "";
|
|
$userName = ApiService::decryptData($p["user_name"]);
|
|
|
|
// Nettoyer les données (comme dans l'ancienne version)
|
|
$nom = str_replace("/", "-", $userName);
|
|
$tournee = str_replace("/", "-", $p["sect_name"]);
|
|
|
|
$aData[] = [
|
|
$dateEve,
|
|
$heureEve,
|
|
$p["user_first_name"],
|
|
$nom,
|
|
$tournee,
|
|
$ptype,
|
|
$p["numero"] . $p["rue_bis"],
|
|
$p["rue"],
|
|
$p["ville"],
|
|
$phabitat,
|
|
$donateur,
|
|
$email,
|
|
$phone,
|
|
$p["montant"],
|
|
$preglement,
|
|
$p["remarque"]
|
|
];
|
|
}
|
|
|
|
return $aData;
|
|
}
|
|
|
|
/**
|
|
* Restaure une opération à partir d'un backup chiffré
|
|
*
|
|
* @param string $backupFilePath Chemin vers le fichier de backup
|
|
* @param int $targetEntiteId ID de l'entité cible (pour restauration cross-entité)
|
|
* @return array Résultat de la restauration
|
|
* @throws Exception En cas d'erreur de restauration
|
|
*/
|
|
public function restoreFromBackup(string $backupFilePath, int $targetEntiteId): array {
|
|
try {
|
|
// Initialiser le service de chiffrement
|
|
$backupService = new BackupEncryptionService();
|
|
|
|
// Lire et déchiffrer le backup
|
|
$backupData = $backupService->readBackupFile($backupFilePath);
|
|
|
|
// Valider la structure du backup
|
|
if (!isset($backupData['operation']) || !isset($backupData['export_metadata'])) {
|
|
throw new Exception('Structure de backup invalide');
|
|
}
|
|
|
|
$operationData = $backupData['operation'];
|
|
$originalEntiteId = $backupData['export_metadata']['source_entite_id'];
|
|
|
|
// Commencer la transaction
|
|
$this->db->beginTransaction();
|
|
|
|
// Créer la nouvelle opération
|
|
$newOperationId = $this->restoreOperation($operationData, $targetEntiteId);
|
|
|
|
// Restaurer les utilisateurs (si même entité)
|
|
if ($targetEntiteId === $originalEntiteId && isset($backupData['users'])) {
|
|
$this->restoreUsers($backupData['users'], $newOperationId);
|
|
}
|
|
|
|
// Restaurer les secteurs
|
|
if (isset($backupData['sectors'])) {
|
|
$this->restoreSectors($backupData['sectors'], $newOperationId);
|
|
}
|
|
|
|
// Restaurer les relations utilisateurs-secteurs
|
|
if (isset($backupData['user_sectors'])) {
|
|
$this->restoreUserSectors($backupData['user_sectors'], $newOperationId);
|
|
}
|
|
|
|
// Restaurer les passages
|
|
if (isset($backupData['passages'])) {
|
|
$this->restorePassages($backupData['passages'], $newOperationId);
|
|
}
|
|
|
|
$this->db->commit();
|
|
|
|
LogService::log('Restauration de backup réussie', [
|
|
'level' => 'info',
|
|
'backup_file' => $backupFilePath,
|
|
'original_operation_id' => $operationData['id'],
|
|
'new_operation_id' => $newOperationId,
|
|
'target_entite_id' => $targetEntiteId,
|
|
'original_entite_id' => $originalEntiteId
|
|
]);
|
|
|
|
return [
|
|
'success' => true,
|
|
'new_operation_id' => $newOperationId,
|
|
'original_operation_id' => $operationData['id'],
|
|
'restored_data' => [
|
|
'operation' => true,
|
|
'users' => isset($backupData['users']) && $targetEntiteId === $originalEntiteId,
|
|
'sectors' => isset($backupData['sectors']),
|
|
'user_sectors' => isset($backupData['user_sectors']),
|
|
'passages' => isset($backupData['passages'])
|
|
]
|
|
];
|
|
} catch (Exception $e) {
|
|
$this->db->rollBack();
|
|
|
|
LogService::log('Erreur lors de la restauration du backup', [
|
|
'level' => 'error',
|
|
'backup_file' => $backupFilePath,
|
|
'target_entite_id' => $targetEntiteId,
|
|
'error' => $e->getMessage()
|
|
]);
|
|
|
|
throw $e;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restaure les données de l'opération
|
|
*/
|
|
private function restoreOperation(array $operationData, int $targetEntiteId): int {
|
|
$stmt = $this->db->prepare('
|
|
INSERT INTO operations (
|
|
fk_entite, libelle, date_deb, date_fin, chk_distinct_sectors,
|
|
fk_user_creat, chk_active, created_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, 0, NOW())
|
|
');
|
|
|
|
$userId = Session::getUserId() ?? 1;
|
|
|
|
$stmt->execute([
|
|
$targetEntiteId,
|
|
$operationData['libelle'] . ' (Restaurée)',
|
|
$operationData['date_deb'],
|
|
$operationData['date_fin'],
|
|
$operationData['chk_distinct_sectors'] ?? 0,
|
|
$userId
|
|
]);
|
|
|
|
return (int)$this->db->lastInsertId();
|
|
}
|
|
|
|
/**
|
|
* Restaure les utilisateurs (uniquement si même entité)
|
|
*/
|
|
private function restoreUsers(array $users, int $newOperationId): void {
|
|
foreach ($users as $user) {
|
|
// Vérifier si l'utilisateur existe déjà
|
|
$stmt = $this->db->prepare('SELECT id FROM users WHERE id = ?');
|
|
$stmt->execute([$user['id']]);
|
|
|
|
if ($stmt->fetch()) {
|
|
// Associer l'utilisateur existant à la nouvelle opération
|
|
$stmt = $this->db->prepare('
|
|
INSERT IGNORE INTO ope_users (fk_operation, fk_user, chk_active, created_at)
|
|
VALUES (?, ?, 1, NOW())
|
|
');
|
|
$stmt->execute([$newOperationId, $user['id']]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restaure les secteurs
|
|
*/
|
|
private function restoreSectors(array $sectors, int $newOperationId): void {
|
|
foreach ($sectors as $sector) {
|
|
$stmt = $this->db->prepare('
|
|
INSERT INTO ope_sectors (
|
|
fk_operation, libelle, color, chk_active, created_at
|
|
) VALUES (?, ?, ?, 1, NOW())
|
|
');
|
|
|
|
$stmt->execute([
|
|
$newOperationId,
|
|
$sector['libelle'],
|
|
$sector['color']
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restaure les relations utilisateurs-secteurs
|
|
*/
|
|
private function restoreUserSectors(array $userSectors, int $newOperationId): void {
|
|
foreach ($userSectors as $us) {
|
|
// Trouver le nouveau secteur par son libellé
|
|
$stmt = $this->db->prepare('
|
|
SELECT id FROM ope_sectors
|
|
WHERE fk_operation = ? AND libelle = ?
|
|
LIMIT 1
|
|
');
|
|
$stmt->execute([$newOperationId, $us['libelle'] ?? '']);
|
|
$newSector = $stmt->fetch();
|
|
|
|
if ($newSector) {
|
|
$stmt = $this->db->prepare('
|
|
INSERT IGNORE INTO ope_users_sectors (
|
|
fk_operation, fk_sector, fk_user, chk_active, created_at
|
|
) VALUES (?, ?, ?, 1, NOW())
|
|
');
|
|
|
|
$stmt->execute([
|
|
$newOperationId,
|
|
$newSector['id'],
|
|
$us['fk_user']
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Restaure les passages
|
|
*/
|
|
private function restorePassages(array $passages, int $newOperationId): void {
|
|
foreach ($passages as $passage) {
|
|
$stmt = $this->db->prepare('
|
|
INSERT INTO ope_pass (
|
|
fk_operation, fk_user, fk_sector, fk_type, passed_at,
|
|
numero, rue_bis, rue, ville, fk_habitat, appt, niveau,
|
|
encrypted_name, encrypted_email, encrypted_phone,
|
|
montant, fk_type_reglement, remarque, chk_active, created_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, NOW())
|
|
');
|
|
|
|
$stmt->execute([
|
|
$newOperationId,
|
|
$passage['fk_user'],
|
|
$passage['fk_sector'],
|
|
$passage['fk_type'],
|
|
$passage['passed_at'],
|
|
$passage['numero'],
|
|
$passage['rue_bis'],
|
|
$passage['rue'],
|
|
$passage['ville'],
|
|
$passage['fk_habitat'],
|
|
$passage['appt'],
|
|
$passage['niveau'],
|
|
$passage['encrypted_name'],
|
|
$passage['encrypted_email'],
|
|
$passage['encrypted_phone'],
|
|
$passage['montant'],
|
|
$passage['fk_type_reglement'],
|
|
$passage['remarque']
|
|
]);
|
|
}
|
|
}
|
|
}
|