feat: Version 3.5.2 - Configuration Stripe et gestion des immeubles
- Configuration complète Stripe pour les 3 environnements (DEV/REC/PROD) * DEV: Clés TEST Pierre (mode test) * REC: Clés TEST Client (mode test) * PROD: Clés LIVE Client (mode live) - Ajout de la gestion des bases de données immeubles/bâtiments * Configuration buildings_database pour DEV/REC/PROD * Service BuildingService pour enrichissement des adresses - Optimisations pages et améliorations ergonomie - Mises à jour des dépendances Composer - Nettoyage des fichiers obsolètes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
471
api/scripts/migration2/php/migrate_from_backup.php
Executable file
471
api/scripts/migration2/php/migrate_from_backup.php
Executable file
@@ -0,0 +1,471 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Script de migration v2 - Architecture modulaire
|
||||
*
|
||||
* Utilise les migrators spécialisés pour une migration hiérarchique par opération.
|
||||
* Source fixe: geosector (synchronisée 2x/jour par PM7 depuis nx4)
|
||||
* Cible: dva_geo (développement), rca_geo (recette) ou pra_geo (production)
|
||||
*
|
||||
* Usage:
|
||||
* Migration d'une entité:
|
||||
* php migrate_from_backup.php --mode=entity --entity-id=2
|
||||
*
|
||||
* Migration globale (toutes les entités):
|
||||
* php migrate_from_backup.php --mode=global
|
||||
*
|
||||
* Avec environnement explicite:
|
||||
* php migrate_from_backup.php --env=dva --mode=entity --entity-id=2
|
||||
*/
|
||||
|
||||
// Inclure ApiService pour le chiffrement
|
||||
require_once dirname(__DIR__, 3) . '/src/Services/ApiService.php';
|
||||
|
||||
// Inclure les classes v2
|
||||
require_once __DIR__ . '/lib/DatabaseConfig.php';
|
||||
require_once __DIR__ . '/lib/MigrationLogger.php';
|
||||
require_once __DIR__ . '/lib/DatabaseConnection.php';
|
||||
require_once __DIR__ . '/lib/UserMigrator.php';
|
||||
require_once __DIR__ . '/lib/PassageMigrator.php';
|
||||
require_once __DIR__ . '/lib/SectorMigrator.php';
|
||||
require_once __DIR__ . '/lib/OperationMigrator.php';
|
||||
|
||||
// Configuration PHP pour les grosses migrations
|
||||
ini_set('memory_limit', '512M');
|
||||
ini_set('max_execution_time', '3600'); // 1 heure max
|
||||
|
||||
class DataMigration
|
||||
{
|
||||
private PDO $sourceDb;
|
||||
private PDO $targetDb;
|
||||
private MigrationLogger $logger;
|
||||
private DatabaseConfig $config;
|
||||
private OperationMigrator $operationMigrator;
|
||||
|
||||
// Options
|
||||
private string $mode;
|
||||
private ?int $entityId;
|
||||
private bool $deleteBefore;
|
||||
|
||||
// Statistiques
|
||||
private array $migrationStats = [];
|
||||
|
||||
public function __construct(string $env, string $mode = 'global', ?int $entityId = null, ?string $logFile = null, bool $deleteBefore = true)
|
||||
{
|
||||
// Initialisation config et logger
|
||||
$this->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 <<<HELP
|
||||
|
||||
🚀 Migration v2 - Architecture modulaire
|
||||
|
||||
USAGE:
|
||||
php migrate_from_backup.php [OPTIONS]
|
||||
|
||||
OPTIONS:
|
||||
--env=ENV Environnement: 'dva' (développement), 'rca' (recette) ou 'pra' (production)
|
||||
Par défaut: auto-détection selon hostname
|
||||
|
||||
--mode=MODE Mode de migration: 'global' ou 'entity'
|
||||
Par défaut: global
|
||||
|
||||
--entity-id=ID ID de l'entité à migrer (requis si mode=entity)
|
||||
|
||||
--log=PATH Fichier de log personnalisé
|
||||
Par défaut: logs/migration_YYYYMMDD_HHMMSS.log
|
||||
|
||||
--delete-before Supprimer les données existantes avant migration
|
||||
Par défaut: true
|
||||
Utiliser --delete-before=false pour désactiver
|
||||
|
||||
--help Afficher cette aide
|
||||
|
||||
EXEMPLES:
|
||||
# Migration d'une entité avec suppression (recommandé)
|
||||
php migrate_from_backup.php --mode=entity --entity-id=2
|
||||
|
||||
# Migration sans suppression (risque de doublons)
|
||||
php migrate_from_backup.php --mode=entity --entity-id=2 --delete-before=false
|
||||
|
||||
# Migration globale de toutes les entités
|
||||
php migrate_from_backup.php --mode=global
|
||||
|
||||
# Spécifier l'environnement manuellement (DVA, RCA ou PRA)
|
||||
php migrate_from_backup.php --env=dva --mode=entity --entity-id=2
|
||||
|
||||
|
||||
HELP;
|
||||
}
|
||||
|
||||
// === POINT D'ENTRÉE ===
|
||||
|
||||
try {
|
||||
$options = parseArguments($argv);
|
||||
|
||||
if ($options['help']) {
|
||||
showHelp();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Valider l'environnement
|
||||
if (!DatabaseConfig::exists($options['env'])) {
|
||||
throw new Exception("Invalid environment: {$options['env']}. Use 'dva', 'rca' or 'pra'");
|
||||
}
|
||||
|
||||
// Créer et exécuter la migration
|
||||
$migration = new DataMigration(
|
||||
$options['env'],
|
||||
$options['mode'],
|
||||
$options['entity-id'],
|
||||
$options['log'],
|
||||
$options['delete-before']
|
||||
);
|
||||
|
||||
$migration->run();
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo "❌ ERREUR: " . $e->getMessage() . "\n";
|
||||
exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user