🚀 Début de la migration Multi-Tenant (Auto-Sync)"; // --------------------------------------------------------- // 1. LECTURE ET PARSING DU FICHIER SCHEMA_FAMILY.SQL // --------------------------------------------------------- $schemaPath = __DIR__ . '/schema_family.sql'; // Modifie si rangé dans /docker/ if (!file_exists($schemaPath)) { $schemaPath = __DIR__ . '/docker/schema_family.sql'; } if (!file_exists($schemaPath)) { die("❌ Impossible de trouver le fichier schema_family.sql"); } $sqlContent = file_get_contents($schemaPath); /** * Analyse le code SQL pour en extraire la structure [Table => [Colonnes]] */ function parseExpectedSchema($sql) { $schema = []; // Récupère tout ce qui se trouve entre CREATE TABLE (...) ENGINE preg_match_all('/CREATE TABLE (?:IF NOT EXISTS )?`?([a-zA-Z0-9_]+)`?\s*\((.*?)\)\s*ENGINE/si', $sql, $tableMatches, PREG_SET_ORDER); foreach($tableMatches as $match) { $tableName = $match[1]; $body = $match[2]; // Sépare les lignes par virgule, en ignorant les virgules entre parenthèses (ex: DECIMAL(10,2) ou ENUM('a','b')) $lines = preg_split('/,(?![^\(]*\))/', $body); $columns = []; foreach($lines as $line) { $line = trim($line); if (empty($line)) continue; // On ignore la déclaration des clés, contraintes et index if (preg_match('/^(PRIMARY KEY|UNIQUE KEY|FOREIGN KEY|KEY|INDEX|FULLTEXT KEY|CONSTRAINT)\b/i', $line)) { continue; } // Extrait le nom de la colonne et sa définition SQL if (preg_match('/^`?([a-zA-Z0-9_]+)`?\s+(.*)$/i', $line, $colMatch)) { $colName = $colMatch[1]; $colDef = rtrim($colMatch[2], ','); $columns[$colName] = $colDef; } } $schema[$tableName] = $columns; } return $schema; } $expectedSchema = parseExpectedSchema($sqlContent); echo "ℹ️ Modèle SQL chargé et analysé : " . count($expectedSchema) . " tables détectées.
"; try { $stmt = $meta_pdo->query("SELECT db_name, name FROM families WHERE is_active = 1"); $families = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($families as $family) { $dbName = $family['db_name']; echo "

🏡 Famille : {$family['name']} ($dbName)

"; try { $pdo = new PDO( "mysql:host=$db_host;dbname=$dbName;charset=utf8mb4", $db_user, $db_pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] ); // --------------------------------------------------------- // 2. EXÉCUTION DU SQL (CRÉATION TABLES MANQUANTES + DONNÉES PAR DÉFAUT) // --------------------------------------------------------- try { $pdo->exec($sqlContent); echo "✅ Structure de base validée (les tables manquantes ont été créées).
"; } catch (PDOException $e) { echo "⚠️ Avertissement SQL brut : " . $e->getMessage() . "
"; } // --------------------------------------------------------- // 3. DIFFING (AJOUT DYNAMIQUE DES COLONNES MANQUANTES) // --------------------------------------------------------- $colsAdded = 0; foreach ($expectedSchema as $table => $expectedCols) { $stmtCol = $pdo->query("SHOW COLUMNS FROM `$table`"); $existingCols = $stmtCol->fetchAll(PDO::FETCH_COLUMN); foreach ($expectedCols as $colName => $colDef) { if (!in_array($colName, $existingCols)) { $pdo->exec("ALTER TABLE `$table` ADD COLUMN `$colName` $colDef"); echo "➕ Nouvelle colonne ajoutée : $table.$colName
"; $colsAdded++; } } } if ($colsAdded === 0) { echo "✅ Toutes les colonnes de toutes les tables sont déjà à jour.
"; } // --------------------------------------------------------- // 4. MIGRATION DES DONNÉES (TRANSFORMATIONS HISTORIQUES) // --------------------------------------------------------- echo "🔄 Exécution des transformations de données héritées...
"; $pdo->exec("ALTER TABLE pf_events MODIFY id INT NOT NULL AUTO_INCREMENT"); $pdo->exec("ALTER TABLE pf_leaves MODIFY id INT NOT NULL AUTO_INCREMENT"); // A. Changement de types spécifiques (Car le parseur n'ajoute que les colonnes manquantes) $pdo->exec("ALTER TABLE pf_budget_items MODIFY category VARCHAR(100) DEFAULT NULL"); // B. Migration de l'historique calendrier (Enfants & Helpers) $helperId = $pdo->query("SELECT id FROM pf_people WHERE role = 'helper' LIMIT 1")->fetchColumn(); if ($helperId) { $pdo->exec("UPDATE pf_events SET event_type = 'HELPER_OFF', person_id = $helperId WHERE event_type = 'OFF_CAROLE'"); $pdo->exec("UPDATE pf_events SET event_type = 'HELPER_EXTRA', person_id = $helperId WHERE event_type = 'EXTRA_OFF_CAROLE'"); } $kidId = $pdo->query("SELECT id FROM pf_people WHERE role IN ('child', 'enfant') ORDER BY id ASC LIMIT 1")->fetchColumn(); if ($kidId) { $pdo->exec("UPDATE pf_events SET event_type = 'CHILD_SICK', person_id = $kidId WHERE event_type = 'PEP_SICK'"); } // C. Mapping Intelligent du Budget (is_estimate) $itemsMigres = 0; $itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'FIXED' WHERE category = 'expense' AND is_estimate = 0"); $itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'FUEL' WHERE category = 'expense' AND is_estimate = 1 AND (mapping_keywords LIKE '%ESSENCE%' OR name LIKE '%gasolina%')"); $itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'SCHOOL' WHERE category = 'expense' AND is_estimate = 1 AND (mapping_keywords LIKE '%ESCOLA%' OR mapping_keywords LIKE '%PARASCOL%' OR name LIKE '%escola%')"); $itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'FMCG' WHERE category = 'expense' AND is_estimate = 1 AND (mapping_keywords LIKE '%FMCG%' OR name LIKE '%F&B%')"); $itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'AUTRES' WHERE category = 'expense'"); $itemsMigres += $pdo->exec("UPDATE pf_budget_items SET category = 'INCOME' WHERE category = 'income'"); // D. Nettoyage des virgules orphelines dans mapping_keywords $budgetCodes = ['INCOME', 'FMCG', 'FUEL', 'SCHOOL', 'HEALTH', 'FIXED', 'SAVINGS', 'AUTRES']; foreach ($budgetCodes as $code) { $pdo->exec("UPDATE pf_budget_items SET mapping_keywords = REPLACE(mapping_keywords, '$code', '') WHERE mapping_keywords LIKE '%$code%'"); } $pdo->exec("UPDATE pf_budget_items SET mapping_keywords = TRIM(BOTH ',' FROM REPLACE(REPLACE(mapping_keywords, ' ', ''), ',,', ','))"); // E. Migration de l'historique des dépenses et des règles d'import $budgetMapping = [ 'Income' => 'INCOME', 'FMCG' => 'FMCG', 'Essence' => 'FUEL', 'School' => 'SCHOOL', 'Frais' => 'FIXED', 'LivretA' => 'SAVINGS', 'Autres' => 'AUTRES' ]; $stmtExp = $pdo->prepare("UPDATE pf_expenses SET category = ? WHERE category = ?"); $stmtRules = $pdo->prepare("UPDATE pf_import_rules SET category = ? WHERE category = ?"); foreach ($budgetMapping as $old => $newCode) { $stmtExp->execute([$newCode, $old]); $stmtRules->execute([$newCode, $old]); } echo "✅ Transformations Data terminées ($itemsMigres prévisions remappées).
"; } catch (PDOException $e) { echo "❌ Erreur sur la base $dbName : " . $e->getMessage() . "
"; } } // FIN DE LA BOUCLE FOREACH FAMILLES echo "

🎉 Synchronisation et Migration terminées avec succès !

"; } catch (Exception $e) { die("❌ Erreur fatale Meta DB : " . $e->getMessage()); } ?>