#!/usr/bin/env php config = new DatabaseConfig($env); $this->mode = $mode; $this->entityId = $entityId; $this->deleteBefore = $deleteBefore; // Générer le nom du fichier log selon le mode si non spécifié if (!$logFile) { $logDir = dirname(__DIR__, 2) . '/logs'; $timestamp = date('Ymd_His'); if ($mode === 'entity' && $entityId) { $logFile = "{$logDir}/migration_entite_{$entityId}_{$timestamp}.log"; } else { $logFile = "{$logDir}/migration_global_{$timestamp}.log"; } } $this->logger = new MigrationLogger($logFile); // Log header $this->logHeader(); // Connexions $dbConnection = new DatabaseConnection($this->config, $this->logger); $dbConnection->connect(); $this->sourceDb = $dbConnection->getSourceDb(); $this->targetDb = $dbConnection->getTargetDb(); // Initialiser les migrators $this->initializeMigrators(); } private function initializeMigrators(): void { // Créer les migrators dans l'ordre de dépendance $passageMigrator = new PassageMigrator($this->sourceDb, $this->targetDb, $this->logger); $sectorMigrator = new SectorMigrator($this->sourceDb, $this->targetDb, $this->logger, $passageMigrator); $userMigrator = new UserMigrator($this->sourceDb, $this->targetDb, $this->logger); $this->operationMigrator = new OperationMigrator( $this->sourceDb, $this->targetDb, $this->logger, $userMigrator, $sectorMigrator ); } public function run(): void { if ($this->mode === 'entity') { if (!$this->entityId) { throw new Exception("entity-id requis en mode entity"); } $this->migrateEntity($this->entityId); } else { $this->migrateAllEntities(); } // Afficher le récapitulatif if (!empty($this->migrationStats)) { $this->logger->logMigrationSummary($this->migrationStats); } $this->logger->separator(); $this->logger->success("🎉 Migration terminée !"); $this->logger->info("📄 Log: " . $this->logger->getLogFile()); } private function migrateEntity(int $entityId): void { $this->logger->separator(); $this->logger->info("🏢 Migration de l'entité ID: {$entityId}"); // Supprimer les données existantes si demandé if ($this->deleteBefore) { $this->deleteEntityData($entityId); } // Migrer l'entité elle-même $this->migrateEntityRecord($entityId); // Migrer les users de l'entité (table centrale users) $this->migrateEntityUsers($entityId); // Récupérer le nom de l'entité pour les stats $stmt = $this->sourceDb->prepare("SELECT libelle FROM users_entites WHERE rowid = :id"); $stmt->execute([':id' => $entityId]); $entityName = $stmt->fetchColumn(); // Récupérer et migrer les opérations $operationIds = $this->operationMigrator->getOperationsToMigrate($entityId); $operations = []; foreach ($operationIds as $oldOperationId) { $operationStats = $this->operationMigrator->migrateOperation($oldOperationId); if ($operationStats) { $operations[] = $operationStats; } } // Stocker les stats pour cette entité $this->migrationStats = [ 'entity' => [ 'id' => $entityId, 'name' => $entityName ?: "Entité #{$entityId}" ], 'operations' => $operations ]; } private function migrateAllEntities(): void { // Récupérer toutes les entités actives $stmt = $this->sourceDb->query("SELECT rowid FROM users_entites WHERE active = 1 ORDER BY rowid"); $entities = $stmt->fetchAll(PDO::FETCH_COLUMN); $this->logger->info("📊 " . count($entities) . " entité(s) à migrer"); $allOperations = []; foreach ($entities as $entityId) { // Sauvegarder les stats actuelles avant de migrer $previousStats = $this->migrationStats; $this->migrateEntity($entityId); // Agréger les opérations de toutes les entités if (!empty($this->migrationStats['operations'])) { $allOperations = array_merge($allOperations, $this->migrationStats['operations']); } } // Stocker les stats globales $this->migrationStats = [ 'operations' => $allOperations ]; } private function deleteEntityData(int $entityId): void { $this->logger->separator(); $this->logger->warning("🗑️ Suppression des données de l'entité {$entityId}..."); // Ordre inverse des contraintes FK $tables = [ 'medias' => "fk_entite = {$entityId} OR fk_operation IN (SELECT id FROM operations WHERE fk_entite = {$entityId})", 'ope_pass_histo' => "fk_pass IN (SELECT id FROM ope_pass WHERE fk_operation IN (SELECT id FROM operations WHERE fk_entite = {$entityId}))", 'ope_pass' => "fk_operation IN (SELECT id FROM operations WHERE fk_entite = {$entityId})", 'ope_users_sectors' => "fk_operation IN (SELECT id FROM operations WHERE fk_entite = {$entityId})", 'ope_users' => "fk_operation IN (SELECT id FROM operations WHERE fk_entite = {$entityId})", 'sectors_adresses' => "fk_sector IN (SELECT id FROM ope_sectors WHERE fk_operation IN (SELECT id FROM operations WHERE fk_entite = {$entityId}))", 'ope_sectors' => "fk_operation IN (SELECT id FROM operations WHERE fk_entite = {$entityId})", 'operations' => "fk_entite = {$entityId}", 'users' => "fk_entite = {$entityId}" ]; foreach ($tables as $table => $condition) { $stmt = $this->targetDb->query("DELETE FROM {$table} WHERE {$condition}"); $count = $stmt->rowCount(); if ($count > 0) { $this->logger->info(" ✓ {$table}: {$count} ligne(s) supprimée(s)"); } } $this->logger->success("✓ Suppression terminée"); } private function migrateEntityRecord(int $entityId): void { // Vérifier si existe déjà $stmt = $this->targetDb->prepare("SELECT COUNT(*) FROM entites WHERE id = :id"); $stmt->execute([':id' => $entityId]); if ($stmt->fetchColumn() > 0) { $this->logger->info("Entité {$entityId} existe déjà, skip"); return; } // Récupérer depuis source $stmt = $this->sourceDb->prepare("SELECT * FROM users_entites WHERE rowid = :id"); $stmt->execute([':id' => $entityId]); $entity = $stmt->fetch(PDO::FETCH_ASSOC); if (!$entity) { throw new Exception("Entité {$entityId} non trouvée"); } // Insérer dans cible (schéma geo_app) $stmt = $this->targetDb->prepare(" INSERT INTO entites ( id, encrypted_name, adresse1, adresse2, code_postal, ville, fk_region, fk_type, encrypted_phone, encrypted_mobile, encrypted_email, gps_lat, gps_lng, chk_stripe, encrypted_stripe_id, encrypted_iban, encrypted_bic, chk_demo, chk_mdp_manuel, chk_username_manuel, chk_user_delete_pass, chk_copie_mail_recu, chk_accept_sms, chk_lot_actif, created_at, fk_user_creat, updated_at, fk_user_modif, chk_active ) VALUES ( :id, :encrypted_name, :adresse1, :adresse2, :code_postal, :ville, :fk_region, :fk_type, :encrypted_phone, :encrypted_mobile, :encrypted_email, :gps_lat, :gps_lng, :chk_stripe, :encrypted_stripe_id, :encrypted_iban, :encrypted_bic, :chk_demo, :chk_mdp_manuel, :chk_username_manuel, :chk_user_delete_pass, :chk_copie_mail_recu, :chk_accept_sms, :chk_lot_actif, :created_at, :fk_user_creat, :updated_at, :fk_user_modif, :chk_active ) "); $stmt->execute([ ':id' => $entityId, ':encrypted_name' => $entity['libelle'] ? ApiService::encryptData($entity['libelle']) : '', ':adresse1' => $entity['adresse1'] ?? '', ':adresse2' => $entity['adresse2'] ?? '', ':code_postal' => $entity['cp'] ?? '', ':ville' => $entity['ville'] ?? '', ':fk_region' => $entity['fk_region'], ':fk_type' => $entity['fk_type'] ?? 1, ':encrypted_phone' => $entity['tel1'] ? ApiService::encryptData($entity['tel1']) : '', ':encrypted_mobile' => $entity['tel2'] ? ApiService::encryptData($entity['tel2']) : '', ':encrypted_email' => $entity['email'] ? ApiService::encryptSearchableData($entity['email']) : '', ':gps_lat' => $entity['gps_lat'] ?? '', ':gps_lng' => $entity['gps_lng'] ?? '', ':chk_stripe' => 0, ':encrypted_stripe_id' => '', ':encrypted_iban' => $entity['iban'] ? ApiService::encryptData($entity['iban']) : '', ':encrypted_bic' => $entity['bic'] ? ApiService::encryptData($entity['bic']) : '', ':chk_demo' => $entity['demo'] ?? 1, ':chk_mdp_manuel' => $entity['chk_mdp_manuel'] ?? 0, ':chk_username_manuel' => 0, ':chk_user_delete_pass' => 0, ':chk_copie_mail_recu' => $entity['chk_copie_mail_recu'] ?? 0, ':chk_accept_sms' => $entity['chk_accept_sms'] ?? 0, ':chk_lot_actif' => 0, ':created_at' => date('Y-m-d H:i:s'), ':fk_user_creat' => 0, ':updated_at' => $entity['date_modif'], ':fk_user_modif' => $entity['fk_user_modif'] ?? 0, ':chk_active' => $entity['active'] ?? 1 ]); $this->logger->success("✓ Entité {$entityId} migrée"); } private function migrateEntityUsers(int $entityId): void { $stmt = $this->sourceDb->prepare("SELECT * FROM users WHERE fk_entite = :entity_id AND active = 1"); $stmt->execute([':entity_id' => $entityId]); $users = $stmt->fetchAll(PDO::FETCH_ASSOC); $count = 0; foreach ($users as $user) { // Vérifier si existe déjà $stmt = $this->targetDb->prepare("SELECT COUNT(*) FROM users WHERE id = :id"); $stmt->execute([':id' => $user['rowid']]); if ($stmt->fetchColumn() > 0) { continue; // Skip si existe } // Insérer l'utilisateur $stmt = $this->targetDb->prepare(" INSERT INTO users ( id, fk_entite, fk_role, first_name, encrypted_name, encrypted_user_name, user_pass_hash, encrypted_email, encrypted_phone, encrypted_mobile, created_at, fk_user_creat, updated_at, fk_user_modif, chk_active ) VALUES ( :id, :fk_entite, :fk_role, :first_name, :encrypted_name, :encrypted_user_name, :user_pass_hash, :encrypted_email, :encrypted_phone, :encrypted_mobile, :created_at, :fk_user_creat, :updated_at, :fk_user_modif, :chk_active ) "); $stmt->execute([ ':id' => $user['rowid'], ':fk_entite' => $user['fk_entite'], ':fk_role' => $user['fk_role'], ':first_name' => $user['prenom'], ':encrypted_name' => ApiService::encryptData($user['libelle']), // Chiffrer avec IV aléatoire ':encrypted_user_name' => ApiService::encryptSearchableData($user['username']), ':user_pass_hash' => $user['userpswd'], // Hash bcrypt du mot de passe ':encrypted_email' => $user['email'] ? ApiService::encryptSearchableData($user['email']) : null, ':encrypted_phone' => $user['telephone'] ? ApiService::encryptData($user['telephone']) : null, ':encrypted_mobile' => $user['mobile'] ? ApiService::encryptData($user['mobile']) : null, ':created_at' => $user['date_creat'], ':fk_user_creat' => $user['fk_user_creat'], ':updated_at' => $user['date_modif'], ':fk_user_modif' => $user['fk_user_modif'], ':chk_active' => $user['active'] ]); $count++; } $this->logger->success("✓ {$count} utilisateur(s) de l'entité migré(s)"); } private function logHeader(): void { $this->logger->separator(); $this->logger->info("🚀 Migration v2 - Architecture modulaire"); $this->logger->info("📅 Date: " . date('Y-m-d H:i:s')); $this->logger->info("🌍 Environnement: " . $this->config->getEnvName()); $this->logger->info("🔧 Mode: " . $this->mode); if ($this->entityId) { $this->logger->info("🏢 Entité: " . $this->entityId); } $this->logger->info("🗑️ Suppression avant: " . ($this->deleteBefore ? 'OUI' : 'NON')); $this->logger->separator(); } } // === GESTION DES ARGUMENTS CLI === function parseArguments(array $argv): array { $options = [ 'env' => DatabaseConfig::autoDetect(), 'mode' => 'global', 'entity-id' => null, 'log' => null, 'delete-before' => true, 'help' => false ]; foreach ($argv as $arg) { if ($arg === '--help') { $options['help'] = true; } elseif (preg_match('/^--env=(.+)$/', $arg, $matches)) { $options['env'] = $matches[1]; } elseif (preg_match('/^--mode=(.+)$/', $arg, $matches)) { $options['mode'] = $matches[1]; } elseif (preg_match('/^--entity-id=(\d+)$/', $arg, $matches)) { $options['entity-id'] = (int)$matches[1]; } elseif (preg_match('/^--log=(.+)$/', $arg, $matches)) { $options['log'] = $matches[1]; } elseif ($arg === '--delete-before=false') { $options['delete-before'] = false; } } return $options; } function showHelp(): void { echo <<run(); } catch (Exception $e) { echo "❌ ERREUR: " . $e->getMessage() . "\n"; exit(1); }