From 9690f1884cac6d4fc2ad3d309ee9628da5dbfee4 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 26 May 2026 09:45:29 -0400 Subject: [PATCH 1/4] Refactor batch processing functions to include epr_id in summary calculations and improve bib assignment logic --- .vscode/settings.json | 25 +++ ajax_bib_range.php | 4 +- inc_footer_scripts.php | 7 +- php/inc_fx_promoteur.php | 403 ++++++++++++++++++++++++--------------- 4 files changed, 281 insertions(+), 158 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..b007321 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,25 @@ +{ + "sqltools.connections": [ + { + "mysqlOptions": { + "authProtocol": "default", + "enableSsl": "Disabled" + }, + "ssh": "Enabled", + "previewLimit": 50, + "server": "localhost", + "port": 3306, + "sshOptions": { + "port": 7822, + "host": "104.254.182.111", + "username": "devinscription", + "password": "M-G7+[NdA34H" + }, + "driver": "MariaDB", + "name": "astral(dev) ms1inscription", + "username": "devinscription_bd", + "password": "x$@_v0-l_I((", + "database": "devinscription_preprod" + } + ] +} \ No newline at end of file diff --git a/ajax_bib_range.php b/ajax_bib_range.php index 7a2e7a4..60fba08 100644 --- a/ajax_bib_range.php +++ b/ajax_bib_range.php @@ -394,7 +394,7 @@ if ($action == 'batch_go') { $nextBib = fxGetNextAvailableBib($epr_id, $tabRanges); $simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation'); - $summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants); + $summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id); $info = "{$summary['nb_dispo']} disponibles / {$summary['nb_participants']} à assigner → manque {$summary['nb_manque']}"; // ===================== @@ -451,7 +451,7 @@ if ($action == 'batch_apply') { return in_array((int)$r['epr_bib_id'], $ids); }); - $summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants); + $summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id); $assigned = count($exec); $total = count($participants); diff --git a/inc_footer_scripts.php b/inc_footer_scripts.php index 9c876dd..234adcc 100644 --- a/inc_footer_scripts.php +++ b/inc_footer_scripts.php @@ -2039,7 +2039,12 @@ if ($strLangue == 'fr') { if (totalEl) totalEl.textContent = data.info.total; if (avecEl) avecEl.textContent = data.info.avec_bib; syncBibUiState(row); - let sansBib = parseInt(data.info.sans_bib || 0, 10); + let sansBib = parseInt( + (data.info.a_assigner !== undefined && data.info.a_assigner !== null) + ? data.info.a_assigner + : (data.info.sans_bib || 0), + 10 + ); if (isNaN(sansBib)) sansBib = 0; let isResetMode = row.dataset.mode === 'reset'; let message = ''; diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index de7fa5a..426c341 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -2958,11 +2958,14 @@ function fxInfosBibEpreuve($epr_id){ $row = $objDatabase->fxGetRow($sql); - return [ + $info = [ 'total' => (int)($row['total'] ?? 0), 'avec_bib' => (int)($row['avec_bib'] ?? 0), - 'sans_bib' => (int)($row['sans_bib'] ?? 0) + 'sans_bib' => (int)($row['sans_bib'] ?? 0), + 'a_assigner' => fxCountBatchAssignUnits($epr_id) ]; + + return $info; } function renderBibRanges($tabBibRanges, $epr_id = 0, $mode = 'assign') { //print_rsl($tabBibRanges); @@ -3497,201 +3500,291 @@ function fxGetNextAvailableBib($epr_id, $tabRanges) { // 👉 aucun BIB disponible return null; } -function fxProcessBatchAssignments($epr_id, $participants, $tabRanges, $mode = 'simulation') { +function fxLoadUsedBibsForBatch($epr_id) { global $objDatabase; - $assignments = []; -$teamMap = []; + $usedBib = []; + $sql = " + SELECT no_bib + FROM resultats_participants + WHERE epr_id = " . intval($epr_id) . " + AND no_bib IS NOT NULL + AND no_bib != '' + AND no_bib > 0 + "; + $rows = $objDatabase->fxGetResults($sql); -$updatedTeams = []; -$teamBibMap = []; -$nextTeamNumber = 1; - if (empty($participants) || empty($tabRanges)) { - return $assignments; + foreach ($rows as $r) { + $usedBib[(int)$r['no_bib']] = true; } - $epr_id = intval($epr_id); + return $usedBib; +} - // ===================== - // MODE SIMULATION → cache en mémoire - // MODE EXECUTE → on ne fait PAS confiance au cache - // ===================== - $usedBib = []; +function fxFindNextBatchBib($epr_id, $tabRanges, $mode, &$usedBib) { + global $objDatabase; - if ($mode === 'simulation') { + foreach ($tabRanges as $range) { + $start = (int)$range['start']; + $end = (int)$range['end']; - // Charger les BIB déjà utilisés (1 seule fois) - $sql = " - SELECT no_bib - FROM resultats_participants - WHERE epr_id = $epr_id - AND no_bib IS NOT NULL - AND no_bib != '' - "; - - $rows = $objDatabase->fxGetResults($sql); - - foreach ($rows as $r) { - $usedBib[(int)$r['no_bib']] = true; + for ($i = $start; $i <= $end; $i++) { + if ($mode === 'simulation') { + if (!isset($usedBib[$i])) { + $usedBib[$i] = true; + return $i; + } + } else { + $sqlCheck = " + SELECT COUNT(*) + FROM resultats_participants + WHERE epr_id = " . intval($epr_id) . " + AND no_bib = $i + "; + if ((int)$objDatabase->fxGetVar($sqlCheck) === 0) { + return $i; + } + } } } - // ===================== - // BOUCLE PRINCIPALE - // ===================== - foreach ($participants as $p) { - - $par_id = (int)$p['par_id']; -$teamKey = (int)($p['pec_id_original'] ?? 0); -$teamMode = (int)($p['epr_bib_team_mode'] ?? 1); -$teamNumber = 0; - -$teamMode = (int)($p['epr_bib_team_mode'] ?? 1); - if ($teamMode !== 3 && $teamKey > 0) { - - if (!isset($teamMap[$teamKey])) { - - $teamMap[$teamKey] = $nextTeamNumber; - $nextTeamNumber++; - } - - $teamNumber = $teamMap[$teamKey]; -} -$foundBib = null; -if ($teamMode === 3 && $teamKey > 0) { - - if (isset($teamBibMap[$teamKey])) { - - $foundBib = $teamBibMap[$teamKey]; - $teamNumber = $teamBibMap[$teamKey]; - } + return null; } +function fxApplyBatchParticipantBib($epr_id, $par_id, $foundBib) { + global $objDatabase; - // ===================== - // CHERCHER BIB LIBRE - // ===================== - if ($foundBib === null) { + $sqlUpdate = " + UPDATE resultats_participants + SET no_bib = " . intval($foundBib) . ", + par_date_bib = IFNULL(par_date_bib, NOW()) + WHERE par_id = " . intval($par_id) . " + AND is_cancelled = 0 + AND (no_bib IS NULL OR no_bib = '' OR no_bib = 0) + "; - foreach ($tabRanges as $range) { + $objDatabase->fxQuery($sqlUpdate); +} - $start = (int)$range['start']; - $end = (int)$range['end']; +function fxApplyBatchTeamNumber($epr_id, $teamKey, $teamNumber, &$updatedTeams) { + global $objDatabase; - for ($i = $start; $i <= $end; $i++) { - - // ===================== - // SIMULATION - // ===================== - if ($mode === 'simulation') { - - if (!isset($usedBib[$i])) { - $foundBib = $i; - $usedBib[$i] = true; // réserver - break 2; - } - - } else { - // ===================== - // EXECUTE (sécurisé DB) - // ===================== - $sqlCheck = " - SELECT COUNT(*) - FROM resultats_participants - WHERE epr_id = $epr_id - AND no_bib = $i - "; - - $exists = (int)$objDatabase->fxGetVar($sqlCheck); - - if ($exists === 0) { - $foundBib = $i; - - // ÉCRITURE RÉELLE - $sqlUpdate = " - UPDATE resultats_participants - SET no_bib = $i, - par_date_bib = IFNULL(par_date_bib, NOW()) - WHERE par_id = $par_id - AND is_cancelled = 0 - AND (no_bib IS NULL OR no_bib = '' OR no_bib = 0) - "; - - $objDatabase->fxQuery($sqlUpdate); -if ($teamNumber > 0 && !isset($updatedTeams[$teamKey])) { + if ($teamKey <= 0 || $teamNumber <= 0 || isset($updatedTeams[$teamKey])) { + return; + } $sqlTeam = " UPDATE resultats_epreuves_commandees - SET no_equipe = $teamNumber - WHERE pec_id_original = $teamKey - AND epr_id = $epr_id + SET no_equipe = " . intval($teamNumber) . " + WHERE pec_id_original = " . intval($teamKey) . " + AND epr_id = " . intval($epr_id) . " "; $objDatabase->fxQuery($sqlTeam); - $updatedTeams[$teamKey] = true; } - break 2; - } - } - } - } } -if ( - $teamMode === 3 - && $teamKey > 0 - && $foundBib > 0 -) { - if (!isset($teamBibMap[$teamKey])) { +function fxBuildBatchAssignmentRow($p, $foundBib, $teamNumber) { + return [ + 'rol_id' => $p['rol_id'], + 'par_id' => (int)$p['par_id'], + 'par_prenom' => $p['par_prenom'], + 'par_nom' => $p['par_nom'], + 'no_bib' => $foundBib, + 'no_equipe' => $teamNumber, + 'pec_nom_equipe' => $p['pec_nom_equipe'] ?? '', + ]; +} - $teamBibMap[$teamKey] = $foundBib; +function fxGetBatchTeamKey($p) { + return (int)($p['pec_id_original'] ?? $p['pec_id'] ?? 0); +} - $teamNumber = $foundBib; +function fxCountBatchAssignUnits($epr_id, $participants = null) { + global $objDatabase; + + $epr_id = intval($epr_id); + + if ($epr_id <= 0) { + return 0; } -} -if ( - $teamMode === 3 - && $teamKey > 0 - && isset($teamBibMap[$teamKey]) -) { - $foundBib = $teamBibMap[$teamKey]; - $teamNumber = $teamBibMap[$teamKey]; + $sqlEpreuve = " + SELECT epr_equipe, epr_bib_team_mode + FROM inscriptions_epreuves + WHERE epr_id = $epr_id + LIMIT 1 + "; + $tabEpreuve = $objDatabase->fxGetRow($sqlEpreuve); + + $countParticipants = function ($rows) { + return is_array($rows) ? count($rows) : 0; + }; + + if (!$tabEpreuve || (int)$tabEpreuve['epr_equipe'] !== 1) { + if (is_array($participants)) { + return $countParticipants($participants); + } + + return (int)$objDatabase->fxGetVar(" + SELECT COUNT(*) + FROM resultats_participants + WHERE epr_id = $epr_id + AND is_cancelled = 0 + AND (no_bib IS NULL OR no_bib = '' OR no_bib = 0) + AND rol_id NOT IN (3, 4) + "); + } + + $teamMode = (int)($tabEpreuve['epr_bib_team_mode'] ?? 1); + if ($teamMode <= 0) { + $teamMode = 1; + } + + if ($teamMode !== 3) { + if (is_array($participants)) { + return $countParticipants($participants); + } + + return (int)$objDatabase->fxGetVar(" + SELECT COUNT(*) + FROM resultats_participants + WHERE epr_id = $epr_id + AND is_cancelled = 0 + AND (no_bib IS NULL OR no_bib = '' OR no_bib = 0) + AND rol_id NOT IN (3, 4) + "); + } + + if (is_array($participants)) { + $teams = []; + + foreach ($participants as $p) { + $teamKey = fxGetBatchTeamKey($p); + + if ($teamKey > 0) { + $teams[$teamKey] = true; + } + } + + return count($teams); + } + + return (int)$objDatabase->fxGetVar(" + SELECT COUNT(DISTINCT p.pec_id) + FROM resultats_participants p + WHERE p.epr_id = $epr_id + AND p.is_cancelled = 0 + AND (p.no_bib IS NULL OR p.no_bib = '' OR p.no_bib = 0) + AND p.rol_id NOT IN (3, 4) + AND p.pec_id > 0 + "); } - // ===================== - // PLUS DE PLACE → STOP - // ===================== + +function fxProcessBatchAssignmentsSolo($epr_id, $participants, $tabRanges, $mode = 'simulation') { + $assignments = []; + $usedBib = ($mode === 'simulation') ? fxLoadUsedBibsForBatch($epr_id) : []; + + foreach ($participants as $p) { + $foundBib = fxFindNextBatchBib($epr_id, $tabRanges, $mode, $usedBib); if ($foundBib === null) { break; } -if ( - $teamMode === 3 - && $teamKey > 0 -) { - $teamNumber = $foundBib; -} - // ===================== - // STOCKER LE RÉSULTAT - // ===================== -$assignments[] = [ -'rol_id' => $p['rol_id'], - 'par_id' => $par_id, - 'par_prenom' => $p['par_prenom'], - 'par_nom' => $p['par_nom'], - 'no_bib' => $foundBib, - 'no_equipe' => $teamNumber, - 'pec_nom_equipe' => $p['pec_nom_equipe'] ?? '', + if ($mode === 'execute') { + fxApplyBatchParticipantBib($epr_id, (int)$p['par_id'], $foundBib); + } -]; + $assignments[] = fxBuildBatchAssignmentRow($p, $foundBib, 0); } return $assignments; } -function fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants) { - $nbParticipants = count($participants); +function fxProcessBatchAssignmentsEquipe($epr_id, $participants, $tabRanges, $mode, $teamMode) { + global $objDatabase; + + $assignments = []; + $teamMap = []; + $updatedTeams = []; + $teamBibMap = []; + $nextTeamNumber = 1; + $usedBib = ($mode === 'simulation') ? fxLoadUsedBibsForBatch($epr_id) : []; + + foreach ($participants as $p) { + $par_id = (int)$p['par_id']; + $teamKey = fxGetBatchTeamKey($p); + $teamNumber = 0; + $foundBib = null; + + if ($teamMode === 3 && $teamKey > 0 && isset($teamBibMap[$teamKey])) { + $foundBib = $teamBibMap[$teamKey]; + $teamNumber = $foundBib; + } elseif ($teamMode !== 3 && $teamKey > 0) { + if (!isset($teamMap[$teamKey])) { + $teamMap[$teamKey] = $nextTeamNumber; + $nextTeamNumber++; + } + + $teamNumber = $teamMap[$teamKey]; + } + + if ($foundBib === null) { + $foundBib = fxFindNextBatchBib($epr_id, $tabRanges, $mode, $usedBib); + } + + if ($foundBib === null) { + break; + } + + if ($teamMode === 3 && $teamKey > 0) { + $teamBibMap[$teamKey] = $foundBib; + $teamNumber = $foundBib; + } + + if ($mode === 'execute') { + fxApplyBatchParticipantBib($epr_id, $par_id, $foundBib); + fxApplyBatchTeamNumber($epr_id, $teamKey, $teamNumber, $updatedTeams); + } + + $assignments[] = fxBuildBatchAssignmentRow($p, $foundBib, $teamNumber); + } + + return $assignments; +} + +function fxProcessBatchAssignments($epr_id, $participants, $tabRanges, $mode = 'simulation') { + global $objDatabase; + + if (empty($participants) || empty($tabRanges)) { + return []; + } + + $epr_id = intval($epr_id); + $sqlEpreuve = " + SELECT epr_equipe, epr_bib_team_mode + FROM inscriptions_epreuves + WHERE epr_id = $epr_id + LIMIT 1 + "; + $tabEpreuve = $objDatabase->fxGetRow($sqlEpreuve); + + if (!$tabEpreuve || (int)$tabEpreuve['epr_equipe'] !== 1) { + return fxProcessBatchAssignmentsSolo($epr_id, $participants, $tabRanges, $mode); + } + + $teamMode = (int)($tabEpreuve['epr_bib_team_mode'] ?? 1); + if ($teamMode <= 0) { + $teamMode = 1; + } + + return fxProcessBatchAssignmentsEquipe($epr_id, $participants, $tabRanges, $mode, $teamMode); +} + +function fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id = 0) { + + $nbParticipants = fxCountBatchAssignUnits($epr_id, $participants); $nbDispo = 0; if (!empty($tabRangesInfos)) { From 517ff7dff6c9db3684d1424d192b52fe31bc89eb Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 26 May 2026 10:11:11 -0400 Subject: [PATCH 2/4] Enhance bib assignment logic in batch processing functions to support team modes and prevent duplicates. Add detailed documentation for clarity. --- php/inc_fx_promoteur.php | 48 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index 426c341..b744df9 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -3521,25 +3521,55 @@ function fxLoadUsedBibsForBatch($epr_id) { return $usedBib; } -function fxFindNextBatchBib($epr_id, $tabRanges, $mode, &$usedBib) { +/** + * Cherche le prochain dossard libre dans les séquences sélectionnées. + * + * Règle anti-doublon (mode execute / GO) : + * - solo ou équipe « dossards individuels » : un numéro = une personne (COUNT participants) + * - équipe « 1 équipe = 1 dossard » : un numéro = une équipe (COUNT DISTINCT pec_id) + * → plusieurs coéquipiers peuvent partager le même no_bib + * + * @param int $teamMode epr_bib_team_mode (1 = individuel, 3 = 1 équipe = 1 dossard) + */ +function fxFindNextBatchBib($epr_id, $tabRanges, $mode, &$usedBib, $teamMode = 1) { global $objDatabase; + $epr_id = intval($epr_id); + $teamMode = (int)$teamMode; + foreach ($tabRanges as $range) { $start = (int)$range['start']; $end = (int)$range['end']; for ($i = $start; $i <= $end; $i++) { if ($mode === 'simulation') { + // Simulation : cache mémoire (même logique solo/équipe via $teamBibMap en amont) if (!isset($usedBib[$i])) { $usedBib[$i] = true; return $i; } + } elseif ($teamMode === 3) { + // GO — mode « 1 équipe = 1 dossard » + // Le numéro est libre s'il n'est pas déjà pris par une autre équipe. + $sqlCheck = " + SELECT COUNT(DISTINCT pec_id) + FROM resultats_participants + WHERE epr_id = $epr_id + AND no_bib = $i + AND is_cancelled = 0 + AND pec_id > 0 + "; + if ((int)$objDatabase->fxGetVar($sqlCheck) === 0) { + return $i; + } } else { + // GO — solo ou dossards individuels : un numéro ne peut servir qu'à une personne $sqlCheck = " SELECT COUNT(*) FROM resultats_participants - WHERE epr_id = " . intval($epr_id) . " + WHERE epr_id = $epr_id AND no_bib = $i + AND is_cancelled = 0 "; if ((int)$objDatabase->fxGetVar($sqlCheck) === 0) { return $i; @@ -3551,6 +3581,11 @@ function fxFindNextBatchBib($epr_id, $tabRanges, $mode, &$usedBib) { return null; } +/** + * Écrit le no_bib d'un participant (batch GO). + * Note : en mode « 1 équipe = 1 dossard », plusieurs lignes peuvent légitimement + * recevoir le même no_bib — ce n'est pas un doublon erroné. + */ function fxApplyBatchParticipantBib($epr_id, $par_id, $foundBib) { global $objDatabase; @@ -3708,6 +3743,8 @@ function fxProcessBatchAssignmentsEquipe($epr_id, $participants, $tabRanges, $mo $assignments = []; $teamMap = []; $updatedTeams = []; + // Mode 3 : mémorise le dossard déjà choisi pour chaque équipe (pec_id) + // → les coéquipiers réutilisent ce numéro sans repasser par fxFindNextBatchBib $teamBibMap = []; $nextTeamNumber = 1; $usedBib = ($mode === 'simulation') ? fxLoadUsedBibsForBatch($epr_id) : []; @@ -3719,9 +3756,11 @@ function fxProcessBatchAssignmentsEquipe($epr_id, $participants, $tabRanges, $mo $foundBib = null; if ($teamMode === 3 && $teamKey > 0 && isset($teamBibMap[$teamKey])) { + // Coéquipier suivant : même dossard que le premier membre déjà traité $foundBib = $teamBibMap[$teamKey]; $teamNumber = $foundBib; } elseif ($teamMode !== 3 && $teamKey > 0) { + // Mode dossards individuels : no_equipe séquentiel (1, 2, 3…) partagé par l'équipe if (!isset($teamMap[$teamKey])) { $teamMap[$teamKey] = $nextTeamNumber; $nextTeamNumber++; @@ -3731,7 +3770,8 @@ function fxProcessBatchAssignmentsEquipe($epr_id, $participants, $tabRanges, $mo } if ($foundBib === null) { - $foundBib = fxFindNextBatchBib($epr_id, $tabRanges, $mode, $usedBib); + // Premier membre d'une équipe (mode 3) ou chaque participant (mode 1) + $foundBib = fxFindNextBatchBib($epr_id, $tabRanges, $mode, $usedBib, $teamMode); } if ($foundBib === null) { @@ -3770,10 +3810,12 @@ function fxProcessBatchAssignments($epr_id, $participants, $tabRanges, $mode = ' "; $tabEpreuve = $objDatabase->fxGetRow($sqlEpreuve); + // Épreuves solo : logique simple, inchangée (1 personne = 1 dossard) if (!$tabEpreuve || (int)$tabEpreuve['epr_equipe'] !== 1) { return fxProcessBatchAssignmentsSolo($epr_id, $participants, $tabRanges, $mode); } + // Épreuves équipe uniquement — voir fxProcessBatchAssignmentsEquipe() $teamMode = (int)($tabEpreuve['epr_bib_team_mode'] ?? 1); if ($teamMode <= 0) { $teamMode = 1; From b6f0b85140c9b3dc6191f240a68ea7eee6471745 Mon Sep 17 00:00:00 2001 From: stephan Date: Tue, 26 May 2026 10:36:35 -0400 Subject: [PATCH 3/4] trie --- php/inc_fx_promoteur.php | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index b744df9..40886f0 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -2550,8 +2550,8 @@ function fxUpdateQuantites($intEpreuve, $intQuantite, $strLangue) { } -// v2 -function renderBatchConfig($tabBibAssignments, $strLangue = 'fr') { +// v2 — options d'ordre batch (table bib_assignements) +function renderBatchConfig($tabBibAssignments, $strLangue = 'fr', $isTeamEpreuve = false) { ob_start(); ?> @@ -2562,7 +2562,13 @@ function renderBatchConfig($tabBibAssignments, $strLangue = 'fr') {