asArray()->find($id); } public function getLinkedData(int $id): array { // 1) Lire la config dans la DB par défaut $config = $this->asArray()->find($id); if (!$config) { return []; } // 2) Connexion à la base MS1Timing $db = \Config\Database::connect('ms1timing'); // 3) Lire Course $course = $db->table('courses') ->where('cou_id', $config['cou_id']) ->get() ->getRowArray(); // 4) Lire Event $event = $db->table('evenements') ->where('eve_id', $config['eve_id']) ->get() ->getRowArray(); return [ 'config' => $config, 'course' => $course, 'event' => $event, ]; } public function getLinkedDatatimingpoint(int $id): array { $db = \Config\Database::connect(); // 1) Lire les timing points pour cette course $timingpoints = $db->table('reader_config_timingpoint') ->where('con_id', $id) ->orderBy('tp_trie', 'ASC') ->get() ->getResultArray(); // Si aucun TP, on retourne vide if (empty($timingpoints)) { return ['timingpoint' => []]; } // 2) Préparer liste des tp_id $tpIds = array_column($timingpoints, 'tp_id'); // 3) Construire nom de la table dynamic $detailTable = "resultv2_{$id}_detail"; // 4) UNE SEULE requête SQL pour compter par TP (optimisation majeure) $counts = $db->table($detailTable) ->select('tp_id, COUNT(*) as total') ->whereIn('tp_id', $tpIds) ->groupBy('tp_id') ->get() ->getResultArray(); // 5) Convertir le résultat en tableau associatif: [tp_id => count] $countMap = []; foreach ($counts as $row) { $countMap[$row['tp_id']] = (int) $row['total']; } // 6) Injecter le count dans chaque timing point foreach ($timingpoints as &$tp) { $tpId = $tp['tp_id']; $tp['lecture_count'] = $countMap[$tpId] ?? 0; } return [ 'timingpoint' => $timingpoints ]; } public function getProcessStatus($con_id) { $table = "resultv2_{$con_id}_lecture"; $row = $this->db->query("SELECT process_status FROM `$table` LIMIT 1")->getRow(); return $row ? $row->process_status : null; } public function getStatsByEventName(string $eventName): array { $db = \Config\Database::connect(); // DB par défaut $results = $db->table('reader_lectures') ->select('chrinfo_location, COUNT(*) AS total, MIN(chrinfo_time) AS start_time, MAX(chrinfo_time) AS end_time') ->where('chrinfo_event_name', $eventName) ->where('chrinfo_type_transaction !=', 'NEW LOCATION') ->groupBy('chrinfo_location') ->get() ->getResultArray(); // Transformer en tableau avec clé 1,2,3... $final = []; $i = 1; foreach ($results as $row) { $final[$i] = [ 'location' => $row['chrinfo_location'], 'count' => (int)$row['total'], 'start_time' => $row['start_time'], 'end_time' => $row['end_time'], 'icon' => 'lucide--eye', 'bg_color' => '#F7B6A8' ]; $i++; } return $final; } public function getEventList(): array { // 1) Lire tous les événements $events = $this->asArray() ->findAll(); $final = []; // 2) Ajouter les données liées pour chaque ligne foreach ($events as $event) { $linked = $this->getLinkedData($event['con_id']); $final[] = [ 'config' => $linked['config'], 'course' => $linked['course'], 'event' => $linked['event'], ]; } // 3) Trier par eve_date_debut DESC (du plus récent au plus ancien) usort($final, function($a, $b) { return strcmp($b['event']['eve_date_debut'], $a['event']['eve_date_debut']); }); return $final; } /** * Duplique les tables de résultats sur la connexion ms1timing_slave * à partir des tables *_template vers *_{$courseId}. * * @param int $courseId ex: 2152 * @return array liste des statuts par table */ public function duplicateResultTables(int $courseId): array { // Connexion à la base ms1timing_slave (déjà définie dans Database.php) $db = \Config\Database::connect(); // Suffixes des tables $suffixes = [ 'classement', 'detail', 'log', 'pause', 'lecture', 'lecture_statut' ]; $results = []; foreach ($suffixes as $suffix) { // Nouveau format demandé $templateTable = "resultv2_template_{$suffix}"; $newTable = "resultv2_{$courseId}_{$suffix}"; // Vérifier que la table template existe if (! $db->tableExists($templateTable)) { $results[] = [ 'table' => $newTable, 'status' => 'template_missing', 'message'=> "La table template {$templateTable} n’existe pas.", ]; continue; } // Si la nouvelle table existe déjà, on ne la touche pas if ($db->tableExists($newTable)) { $results[] = [ 'table' => $newTable, 'status' => 'exists', 'message'=> "La table {$newTable} existe déjà. Aucune action.", ]; continue; } // Création + remplissage dans une transaction $db->transStart(); // CREATE TABLE nouveau LIKE template $db->query( "CREATE TABLE `{$newTable}` LIKE `{$templateTable}`" ); // Cas particulier pour la table lecture : on met directement les bonnes valeurs initiales if ($suffix === 'lecture') { $db->query(" INSERT INTO `{$newTable}` ( id, last_processed_read_id, config_hash, needs_rebuild, process_status, backup_flag, last_rebuild_ts, last_backup_ts, moteur_version, created_at, updated_at ) VALUES ( 1, 0, '', 0, 'IDLE', 0, NULL, NULL, '1.0', NOW(), NOW() ) "); } else { // INSERT INTO new SELECT * FROM template (comportement existant) $db->query( "INSERT INTO `{$newTable}` SELECT * FROM `{$templateTable}`" ); } $db->transComplete(); if ($db->transStatus() === false) { $results[] = [ 'table' => $newTable, 'status' => 'error', 'message'=> "Erreur lors de la duplication de {$newTable}.", ]; } else { $results[] = [ 'table' => $newTable, 'status' => 'created', 'message'=> "Table {$newTable} créée et remplie à partir de {$templateTable}.", ]; } } return $results; } /** * Récupère les timingpoints + toutes les configurations associées * à partir de la table reader_config_detail. * * @param int $id ID du contrôleur (con_id) * @return array Tableau final des timingpoints enrichis */ public function getLinkedDatatimingpointWithConfig(int $id) { //---------------------------------------------------------------------- // 1. Récupération des timingpoints existants (déjà fonctionnel chez toi) //---------------------------------------------------------------------- $data = $this->getLinkedDatatimingpoint($id); // Sécurité : si aucun timingpoint, on retourne tout de suite if (!isset($data['timingpoint']) || empty($data['timingpoint'])) { return $data; } //---------------------------------------------------------------------- // 2. Boucle sur chaque timingpoint (un tp_id à la fois) //---------------------------------------------------------------------- foreach ($data['timingpoint'] as $index => $tp) { $tp_id = $tp['tp_id']; //------------------------------------------------------------------ // 3. Aller chercher toutes les lignes reader_config_detail // où det_ref1_id = tp_id //------------------------------------------------------------------ $builder = $this->db->table('reader_config_detail'); $builder->where('det_ref1_id', $tp_id); $builder->orderBy('det_trie', 'ASC'); $rows = $builder->get()->getResultArray(); //------------------------------------------------------------------ // 4. Aucune config → on continue au suivant //------------------------------------------------------------------ if (empty($rows)) { continue; } //------------------------------------------------------------------ // 5. Préparation des conteneurs de regroupement //------------------------------------------------------------------ $configSets = []; //------------------------------------------------------------------ // 6. Boucle à travers chaque config trouvée //------------------------------------------------------------------ foreach ($rows as $row) { //-------------------------------------------------------------- // 6.1 Extraction du groupe (ex: configs-tp, configs-tp-interval) // → conversion en identifiant propre pour le tableau final //-------------------------------------------------------------- $groupName = !empty($row['det_groupe']) ? str_replace('-', '_', $row['det_groupe']) : 'configs_generic'; // fallback, jamais vide //-------------------------------------------------------------- // 6.2 La clef interne (ex: clef_controleur) //-------------------------------------------------------------- $clef = $row['det_clef']; if (empty($clef)) { // Si jamais c'est vide, on ignore cette ligne continue; } //-------------------------------------------------------------- // 6.3 Préparer la structure si elle n'existe pas encore //-------------------------------------------------------------- if (!isset($configSets[$groupName])) { $configSets[$groupName] = []; } if (!isset($configSets[$groupName][$clef])) { $configSets[$groupName][$clef] = []; } //-------------------------------------------------------------- // 6.4 Ajouter la ligne de configuration //-------------------------------------------------------------- $configSets[$groupName][$clef][] = [ 'value1' => $row['det_value1'], 'value2' => $row['det_value2'], 'value3' => $row['det_value3'], ]; } //------------------------------------------------------------------ // 7. Injection dans le timingpoint final //------------------------------------------------------------------ $data['timingpoint'][$index] = array_merge( $data['timingpoint'][$index], $configSets ); } //---------------------------------------------------------------------- // 8. Retour final //---------------------------------------------------------------------- return $data; } public function getcourseConfig(int $id) { //------------------------------------------------------------------ $builder = $this->db->table('reader_config_detail'); $builder->where('det_ref1_id', $id); $builder->where('det_type', 2); $builder->orderBy('det_trie', 'ASC'); $rows = $builder->get()->getResultArray(); //------------------------------------------------------------------ // 5. Préparation des conteneurs de regroupement //------------------------------------------------------------------ $configSets = []; //------------------------------------------------------------------ // 6. Boucle à travers chaque config trouvée //------------------------------------------------------------------ foreach ($rows as $row) { //-------------------------------------------------------------- // 6.1 Extraction du groupe (ex: configs-tp, configs-tp-interval) // → conversion en identifiant propre pour le tableau final //-------------------------------------------------------------- $groupName = !empty($row['det_groupe']) ? str_replace('-', '_', $row['det_groupe']) : 'configs_generic'; // fallback, jamais vide //-------------------------------------------------------------- // 6.2 La clef interne (ex: clef_controleur) //-------------------------------------------------------------- $clef = $row['det_clef']; if (empty($clef)) { // Si jamais c'est vide, on ignore cette ligne continue; } //-------------------------------------------------------------- // 6.3 Préparer la structure si elle n'existe pas encore //-------------------------------------------------------------- if (!isset($configSets[$groupName])) { $configSets[$groupName] = []; } if (!isset($configSets[$groupName][$clef])) { $configSets[$groupName][$clef] = []; } //-------------------------------------------------------------- // 6.4 Ajouter la ligne de configuration //-------------------------------------------------------------- $output=$row['det_value1']; if($row['det_type_output']!='') $output=format_output($row['det_value1'], $row['det_type_output']); $configSets[$groupName][$clef][] = [ 'value1' => $row['det_value1'], 'value2' => $row['det_value2'], 'value3' => $row['det_value3'], 'output' => $output, ]; } //-------------------------------------------------------------- //---------------------------------------------------------------------- // 8. Retour final //---------------------------------------------------------------------- return $configSets; } public function resetCourse($con_id) { $lectureTable = "resultv2_{$con_id}_lecture"; $detailTable = "resultv2_{$con_id}_detail"; // ------------------------------------------------- // 1) Demander la pause (PAUSE_REQUEST) // ------------------------------------------------- $this->db->query("UPDATE `$lectureTable` SET process_status = 'PAUSE_REQUEST'"); // ------------------------------------------------- // 2) Attendre que Python applique PAUSE // ------------------------------------------------- $timeoutSeconds = 20 * 60; // 20 minutes $waitInterval = 500000; // 0.5 seconde $attempts = ($timeoutSeconds * 1000000) / $waitInterval; $isPaused = false; for ($i = 0; $i < $attempts; $i++) { $row = $this->db->query("SELECT process_status FROM `$lectureTable` LIMIT 1")->getRow(); if ($row && strtoupper($row->process_status) === 'PAUSE') { $isPaused = true; break; } // Attendre 0.5 sec avant de rechecker usleep($waitInterval); } if (!$isPaused) { return "ERREUR : Impossible d'obtenir PAUSE (timeout). Base NON modifiée."; } // ------------------------------------------------- // 3) TRUNCATE de la table detail (sécurisé) // ------------------------------------------------- $this->db->query("TRUNCATE TABLE `$detailTable`"); // ------------------------------------------------- // 4) Remettre last_processed_read_id à 0 // ------------------------------------------------- $this->db->query(" UPDATE `$lectureTable` SET last_processed_read_id = 0 "); // ------------------------------------------------- // 5) Remettre la course en IDLE pour que Python puisse repartir // ------------------------------------------------- $this->db->query("UPDATE `$lectureTable` SET process_status = 'IDLE'"); return "OK : Course réinitialisée."; } }