Livraison d ela gestion des opérations v0.4.0
This commit is contained in:
933
api/src/Services/ExportService.php
Normal file
933
api/src/Services/ExportService.php
Normal file
@@ -0,0 +1,933 @@
|
||||
<?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']
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user