From 588d573703c4e1d28404e116465b26d767e7af0d Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 8 Jul 2026 11:05:00 -0400 Subject: [PATCH] Refactor batch sorting functionality and update version code to 4.72.755 This commit enhances the batch sorting mechanism by introducing new functions to normalize and manage sorting keys for bib assignments. The AJAX handlers are updated to utilize these new sorting parameters, improving the overall flexibility and accuracy of batch operations. Additionally, the version code is incremented to reflect these changes. --- ajax_bib_range.php | 36 +-- js/v2/bib-assignments.js | 75 +++-- php/inc_fx_promoteur.php | 434 ++++++++++++++++++++++---- php/inc_settings.php | 2 +- sql/MSIN-4424-bib-allow-inter-epr.sql | 3 + sql/MSIN-4436-bib-sort-levels.sql | 72 +++++ 6 files changed, 526 insertions(+), 96 deletions(-) create mode 100644 sql/MSIN-4436-bib-sort-levels.sql diff --git a/ajax_bib_range.php b/ajax_bib_range.php index 97f1408..af204f6 100644 --- a/ajax_bib_range.php +++ b/ajax_bib_range.php @@ -433,11 +433,11 @@ if ($action == 'global_batch_panel') { if ($action == 'batch_global_analysis') { $int_eve_id = (int)($_POST['eve_id'] ?? 0); - $ba_id = (int)($_POST['ba_id'] ?? 0); + list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $plans = json_decode($_POST['plans'] ?? '[]', true); $tabPlans = fxBibParseGlobalBatchPlans($plans, $int_eve_id); - if ($int_eve_id <= 0 || $ba_id <= 0 || empty($tabPlans)) { + if ($int_eve_id <= 0 || $strSort1 === '' || empty($tabPlans)) { echo json_encode([ 'success' => false, 'message' => fxBibMsg('bib_v4_global_batch_plan_invalid'), @@ -454,7 +454,7 @@ if ($action == 'batch_global_analysis') { $epr_id = (int)$plan['epr_id']; $ranges = $plan['ranges']; - $participants = fxGetParticipantsForBatchAssign($epr_id, $ba_id); + $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); $tabRangesInfos = fxInfosBibRange($epr_id); $ids = array_map('intval', $ranges); $tabRangesInfos = array_filter($tabRangesInfos, function ($r) use ($ids) { @@ -495,11 +495,11 @@ if ($action == 'batch_global_analysis') { if ($action == 'batch_global_simulate') { $int_eve_id = (int)($_POST['eve_id'] ?? 0); - $ba_id = (int)($_POST['ba_id'] ?? 0); + list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $plans = json_decode($_POST['plans'] ?? '[]', true); $tabPlans = fxBibParseGlobalBatchPlans($plans, $int_eve_id); - if ($int_eve_id <= 0 || $ba_id <= 0 || empty($tabPlans)) { + if ($int_eve_id <= 0 || $strSort1 === '' || empty($tabPlans)) { echo json_encode([ 'success' => false, 'message' => fxBibMsg('bib_v4_global_batch_plan_invalid'), @@ -517,7 +517,7 @@ if ($action == 'batch_global_simulate') { $epr_id = (int)$plan['epr_id']; $ranges = $plan['ranges']; - $participants = fxGetParticipantsForBatchAssign($epr_id, $ba_id); + $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); $tabRanges = fxGetRangesForBatchAssign($epr_id, $ranges); $simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation'); @@ -658,15 +658,10 @@ if ($action == 'batch_go') { exit; } - // Ordre choisi (table bib_assignements) - $ba_id = intval($_POST['ba_id'] ?? 0); + list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); + fxBibSaveEpreuveBatchSort($epr_id, $strSort1, $strSort2); - // DEBUG : on retourne tout pour validation - // ===================== -// TEST fxGetParticipantsForBatchAssign -// ===================== - - $participants = fxGetParticipantsForBatchAssign($epr_id, $ba_id); + $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); // ===================== // UTILISER LA FONCTION PROPRE // ===================== @@ -727,7 +722,7 @@ if ($action == 'batch_apply') { $epr_id = intval($_POST['epr_id'] ?? 0); $ranges = json_decode($_POST['ranges'] ?? '[]', true); - $ba_id = intval($_POST['ba_id'] ?? 0); + list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $lockCheck = fxBibAssertRangesUnlocked($epr_id, is_array($ranges) ? $ranges : []); if (!$lockCheck['success']) { @@ -738,7 +733,9 @@ if ($action == 'batch_apply') { exit; } - $participants = fxGetParticipantsForBatchAssign($epr_id, $ba_id); + fxBibSaveEpreuveBatchSort($epr_id, $strSort1, $strSort2); + + $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); $tabRanges = fxGetRangesForBatchAssign($epr_id, $ranges); $exec = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'execute'); @@ -758,7 +755,7 @@ if ($action == 'batch_apply_chunk') { $epr_id = intval($_POST['epr_id'] ?? 0); $ranges = json_decode($_POST['ranges'] ?? '[]', true); - $ba_id = intval($_POST['ba_id'] ?? 0); + list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $intChunkSize = (int)($_POST['chunk_size'] ?? 50); $intTotalInitial = (int)($_POST['total_initial'] ?? 0); $blnBatchReset = ((int)($_POST['batch_reset'] ?? 0) === 1); @@ -783,11 +780,12 @@ if ($action == 'batch_apply_chunk') { if ($blnBatchReset) { $_SESSION[$strSessionKey] = []; + fxBibSaveEpreuveBatchSort($epr_id, $strSort1, $strSort2); } elseif (!isset($_SESSION[$strSessionKey]) || !is_array($_SESSION[$strSessionKey])) { $_SESSION[$strSessionKey] = []; } - $participants = fxGetParticipantsForBatchAssign($epr_id, $ba_id); + $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); if ($intTotalInitial <= 0) { $intTotalInitial = count($participants) + count($_SESSION[$strSessionKey]); @@ -817,7 +815,7 @@ if ($action == 'batch_apply_chunk') { $_SESSION[$strSessionKey] = array_merge($_SESSION[$strSessionKey], $execChunk); - $intRemaining = count(fxGetParticipantsForBatchAssign($epr_id, $ba_id)); + $intRemaining = count(fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2)); $intProcessed = max(0, $intTotalInitial - $intRemaining); $blnComplete = ($intRemaining === 0); diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js index e398b1e..f39b979 100644 --- a/js/v2/bib-assignments.js +++ b/js/v2/bib-assignments.js @@ -201,13 +201,45 @@ }); } - function bibFetchBatchApplyChunk(row, selected, ba_id, opts) { + function bibGetRowSortKeys(row) { + if (!row) { + return { sort1: '', sort2: '' }; + } + var s1 = row.querySelector('.batch-order'); + var s2 = row.querySelector('.batch-order-2'); + return { + sort1: s1 ? s1.value : '', + sort2: s2 ? s2.value : '' + }; + } + + function bibGetGlobalBatchSortKeys(panel) { + if (!panel) { + return { sort1: '', sort2: '' }; + } + var s1 = panel.querySelector('.bib-global-batch-order'); + var s2 = panel.querySelector('.bib-global-batch-order-2'); + return { + sort1: s1 ? s1.value : '', + sort2: s2 ? s2.value : '' + }; + } + + function bibAppendBatchSortParams(body, sortKeys) { + sortKeys = sortKeys || {}; + return body + + '&sort_1=' + encodeURIComponent(sortKeys.sort1 || '') + + '&sort_2=' + encodeURIComponent(sortKeys.sort2 || ''); + } + + function bibFetchBatchApplyChunk(row, selected, sortKeys, opts) { opts = opts || {}; + sortKeys = sortKeys || bibGetRowSortKeys(row); var body = 'action=batch_apply_chunk' + '&epr_id=' + encodeURIComponent(row.dataset.eprId) + '&ranges=' + encodeURIComponent(JSON.stringify(selected)) - + '&ba_id=' + encodeURIComponent(ba_id) + + bibAppendBatchSortParams('', sortKeys) + '&chunk_size=' + BIB_BATCH_CHUNK_SIZE + '&total_initial=' + (opts.totalInitial || 0) + '&batch_reset=' + (opts.batchReset ? 1 : 0) @@ -232,7 +264,7 @@ }); } - function bibRunBatchApplyChunks(row, selected, ba_id, options) { + function bibRunBatchApplyChunks(row, selected, sortKeys, options) { options = options || {}; var showResult = options.showResult !== false; var manageLoader = options.manageLoader !== false; @@ -255,7 +287,7 @@ return Promise.reject(new Error(bibJs('jsErrGeneric'))); } - return bibFetchBatchApplyChunk(row, selected, ba_id, { + return bibFetchBatchApplyChunk(row, selected, sortKeys, { batchReset: batchReset, totalInitial: totalInitial }).then(function (data) { @@ -438,9 +470,9 @@ var plans = bibCollectGlobalBatchPlans(); var infoEl = bibGlobalBatchPanel.querySelector('.bib-global-batch__info'); - var baSelect = bibGlobalBatchPanel.querySelector('.bib-global-batch-order'); + var sortKeys = bibGetGlobalBatchSortKeys(bibGlobalBatchPanel); - if (!infoEl || !baSelect) { + if (!infoEl) { return Promise.resolve(); } @@ -471,7 +503,7 @@ body: 'action=batch_global_analysis' + '&eve_id=' + encodeURIComponent(moduleBib.dataset.eveId || '') - + '&ba_id=' + encodeURIComponent(baSelect.value) + + bibAppendBatchSortParams('', sortKeys) + '&plans=' + encodeURIComponent(JSON.stringify(plans)) + bibAjaxLangSuffix() }) @@ -503,7 +535,7 @@ } var plans = bibCollectGlobalBatchPlans(); - var baSelect = bibGlobalBatchPanel.querySelector('.bib-global-batch-order'); + var sortKeys = bibGetGlobalBatchSortKeys(bibGlobalBatchPanel); if (plans.length === 0) { alert(bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone')); @@ -515,15 +547,13 @@ return; } - var ba_id = baSelect ? baSelect.value : ''; - fetch('/ajax_bib_range.php', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=batch_global_analysis' + '&eve_id=' + encodeURIComponent(moduleBib.dataset.eveId || '') - + '&ba_id=' + encodeURIComponent(ba_id) + + bibAppendBatchSortParams('', sortKeys) + '&plans=' + encodeURIComponent(JSON.stringify(plans)) + bibAjaxLangSuffix() }) @@ -558,7 +588,7 @@ var pendingBefore = bibRowPendingBibs(row); var offset = completedOffset; - return bibRunBatchApplyChunks(row, plan.ranges, ba_id, { + return bibRunBatchApplyChunks(row, plan.ranges, sortKeys, { showResult: false, manageLoader: false, deferUiRefresh: true, @@ -680,9 +710,9 @@ e.preventDefault(); var bibGlobalBatchPanel = bibGetGlobalBatchPanel(); var plans = bibCollectGlobalBatchPlans(); - var baSelect = bibGlobalBatchPanel - ? bibGlobalBatchPanel.querySelector('.bib-global-batch-order') - : null; + var sortKeys = bibGlobalBatchPanel + ? bibGetGlobalBatchSortKeys(bibGlobalBatchPanel) + : { sort1: '', sort2: '' }; if (plans.length === 0) { alert(bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone')); @@ -695,7 +725,7 @@ body: 'action=batch_global_simulate' + '&eve_id=' + encodeURIComponent(moduleBib.dataset.eveId || '') - + '&ba_id=' + encodeURIComponent(baSelect ? baSelect.value : '') + + bibAppendBatchSortParams('', sortKeys) + '&plans=' + encodeURIComponent(JSON.stringify(plans)) + bibAjaxLangSuffix() }) @@ -727,7 +757,7 @@ }); moduleBib.addEventListener('change', function (e) { - if (!e.target.matches('.bib-global-epr-select, .bib-global-range-select, .bib-global-batch-order')) { + if (!e.target.matches('.bib-global-epr-select, .bib-global-range-select, .bib-global-batch-order, .bib-global-batch-order-2')) { return; } @@ -1694,6 +1724,7 @@ bibSetVisible('.epr-auto-info-group', showAutoReady, row); bibSetVisible('.batch-reset-warning', showBatchReady && modes.reset, row); bibSetVisible('.epr-batch-order-wrap', showBatchReady && !modes.reset, row); + bibSetVisible('.epr-batch-order-wrap--secondary', showBatchReady && !modes.reset, row); bibSetVisible('.epr-action-sim-group', showBatchReady && !modes.reset, row); bibSetVisible('.epr-action-go-group', showBatchReady, row); bibSyncResetAllEprButton(row); @@ -2399,7 +2430,7 @@ return; } let isResetMode = row.dataset.mode === 'reset'; - let ba_id = row.querySelector('.batch-order').value; + let sortKeys = bibGetRowSortKeys(row); bibFetch('/ajax_bib_range.php', { method: 'POST', @@ -2410,7 +2441,7 @@ (isResetMode ? 'action=reset_preview' : 'action=batch_go') + '&epr_id=' + encodeURIComponent(row.dataset.eprId) + '&ranges=' + encodeURIComponent(JSON.stringify(selected)) - + '&ba_id=' + encodeURIComponent(ba_id) + + bibAppendBatchSortParams('', sortKeys) + bibAjaxLangSuffix() }) .then(res => res.json()) @@ -2559,7 +2590,7 @@ return; } let isResetMode = row.dataset.mode === 'reset'; - let ba_id = row.querySelector('.batch-order').value; + let sortKeys = bibGetRowSortKeys(row); if (isResetMode) { bibFetch('/ajax_bib_range.php', { @@ -2571,7 +2602,7 @@ 'action=reset_apply' + '&epr_id=' + encodeURIComponent(row.dataset.eprId) + '&ranges=' + encodeURIComponent(JSON.stringify(selected)) - + '&ba_id=' + encodeURIComponent(ba_id) + + bibAppendBatchSortParams('', sortKeys) + bibAjaxLangSuffix() }) .then(res => res.json()) @@ -2588,7 +2619,7 @@ return; } - bibRunBatchApplyChunks(row, selected, ba_id); + bibRunBatchApplyChunks(row, selected, sortKeys); return; } diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index 5807ac5..d436728 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -2640,8 +2640,339 @@ function renderBibBatchPickPrompt() { return ob_get_clean(); } +// v2 — options d'ordre batch (table bib_assignements + questions par épreuve) + +/** MSIN-4436 — Normalise une clé de tri (ba:5, que:123 ou legacy entier). */ +function fxBibNormalizeSortKey($mix) { + if ($mix === null || $mix === '' || $mix === '0' || $mix === 0) { + return ''; + } + + if (is_numeric($mix) && (int)$mix > 0) { + return 'ba:' . (int)$mix; + } + + $str = trim((string)$mix); + if (preg_match('/^(ba|que):\d+$/', $str)) { + return $str; + } + + return ''; +} + +/** MSIN-4436 — Clé de tri depuis ba_id legacy. */ +function fxBibSortKeyFromBaId($int_ba_id) { + $int_ba_id = (int)$int_ba_id; + return $int_ba_id > 0 ? 'ba:' . $int_ba_id : ''; +} + +/** MSIN-4436 — Tris mémorisés sur l'épreuve (repli sur ba_id). */ +function fxBibGetEpreuveDefaultSortKeys(array $tabEpreuve) { + $strSort1 = fxBibNormalizeSortKey($tabEpreuve['ba_bib_sort_1'] ?? ''); + $strSort2 = fxBibNormalizeSortKey($tabEpreuve['ba_bib_sort_2'] ?? ''); + + if ($strSort1 === '') { + $strSort1 = fxBibSortKeyFromBaId((int)($tabEpreuve['ba_id'] ?? 0)); + } + if ($strSort1 === '') { + $strSort1 = 'ba:2'; + } + + return [$strSort1, $strSort2]; +} + +/** MSIN-4436 — Persiste le tri batch sur l'épreuve (+ ba_id si tri fixe). */ +function fxBibSaveEpreuveBatchSort($epr_id, $sort1, $sort2 = '') { + global $objDatabase; + + $epr_id = (int)$epr_id; + $strSort1 = fxBibNormalizeSortKey($sort1); + $strSort2 = fxBibNormalizeSortKey($sort2); + + if ($epr_id <= 0 || $strSort1 === '') { + return false; + } + + if ($strSort1 === $strSort2) { + $strSort2 = ''; + } + + $intBaId = 0; + if (preg_match('/^ba:(\d+)$/', $strSort1, $m)) { + $intBaId = (int)$m[1]; + } + + $sql = " + UPDATE inscriptions_epreuves + SET ba_bib_sort_1 = '" . $objDatabase->fxEscape($strSort1) . "', + ba_bib_sort_2 = '" . $objDatabase->fxEscape($strSort2) . "' + " . ($intBaId > 0 ? ", ba_id = $intBaId" : '') . " + WHERE epr_id = $epr_id + LIMIT 1 + "; + + return (bool)$objDatabase->fxQuery($sql); +} + +/** MSIN-4436 — Lit sort_1 / sort_2 depuis POST (repli ba_id). */ +function fxBibParseBatchSortFromPost() { + $strSort1 = fxBibNormalizeSortKey($_POST['sort_1'] ?? ''); + $strSort2 = fxBibNormalizeSortKey($_POST['sort_2'] ?? ''); + + if ($strSort1 === '') { + $strSort1 = fxBibNormalizeSortKey($_POST['ba_id'] ?? ''); + } + + if ($strSort1 === '') { + $strSort1 = 'ba:2'; + } + + if ($strSort1 === $strSort2) { + $strSort2 = ''; + } + + return [$strSort1, $strSort2]; +} + +/** MSIN-4436 — Questions éligibles au tri pour une épreuve. */ +function fxBibGetQuestionSortOptions($epr_id, $eve_id, $strLangue = 'fr') { + global $objDatabase; + + $epr_id = (int)$epr_id; + $eve_id = (int)$eve_id; + if ($epr_id <= 0 || $eve_id <= 0) { + return []; + } + + $strCol = ($strLangue === 'en') ? 'que_question_en' : 'que_question_fr'; + $sql = " + SELECT que_id, $strCol AS que_label, que_validation + FROM inscriptions_questions + WHERE que_actif = 1 + AND eve_id = $eve_id + AND (FIND_IN_SET($epr_id, que_epreuves_incluses) OR que_epreuves_incluses = '' OR que_epreuves_incluses IS NULL) + AND que_validation NOT IN ('image', 'pdf') + ORDER BY que_tri, que_id + "; + $rows = $objDatabase->fxGetResults($sql); + if (!is_array($rows)) { + return []; + } + + $out = []; + foreach ($rows as $row) { + $intQueId = (int)($row['que_id'] ?? 0); + if ($intQueId <= 0) { + continue; + } + $strLabel = trim((string)($row['que_label'] ?? '')); + if ($strLabel === '') { + $strLabel = 'Question #' . $intQueId; + } + if (mb_strlen($strLabel) > 72) { + $strLabel = mb_substr($strLabel, 0, 69) . '…'; + } + $out[] = [ + 'key' => 'que:' . $intQueId, + 'label' => $strLabel, + 'group' => 'question', + ]; + } + + return $out; +} + +/** MSIN-4436 — Options de tri (fixes + questions) pour une épreuve. */ +function fxBibGetSortOptionsForEpreuve($epr_id, $eve_id, $strLangue = 'fr', $blnTeamEpreuve = false, $blnIncludeQuestions = true) { + global $objDatabase; + + $out = []; + $rows = $objDatabase->fxGetResults( + "SELECT * FROM bib_assignements WHERE ba_actif = 1 ORDER BY ba_tri" + ); + + if (is_array($rows)) { + foreach ($rows as $row) { + if ((int)($row['ba_equipe'] ?? 0) === 1 && !$blnTeamEpreuve) { + continue; + } + $intBaId = (int)($row['ba_id'] ?? 0); + if ($intBaId <= 0) { + continue; + } + $strLabel = trim((string)($row['ba_nom_' . $strLangue] ?? '')); + if ($strLabel === '') { + $strLabel = trim((string)($row['ba_nom_fr'] ?? '')); + } + $out[] = [ + 'key' => 'ba:' . $intBaId, + 'label' => $strLabel !== '' ? $strLabel : ('ba:' . $intBaId), + 'group' => 'fixed', + ]; + } + } + + if ($blnIncludeQuestions && (int)$epr_id > 0 && (int)$eve_id > 0) { + $out = array_merge($out, fxBibGetQuestionSortOptions($epr_id, $eve_id, $strLangue)); + } + + return $out; +} + +/** MSIN-4436 — Vérifie qu'une question est liée à l'épreuve. */ +function fxBibQuestionSortAllowedForEpreuve($epr_id, $que_id, $eve_id) { + global $objDatabase; + + $epr_id = (int)$epr_id; + $que_id = (int)$que_id; + $eve_id = (int)$eve_id; + if ($epr_id <= 0 || $que_id <= 0 || $eve_id <= 0) { + return false; + } + + $sql = " + SELECT COUNT(*) + FROM inscriptions_questions + WHERE que_id = $que_id + AND eve_id = $eve_id + AND que_actif = 1 + AND que_validation NOT IN ('image', 'pdf') + AND (FIND_IN_SET($epr_id, que_epreuves_incluses) OR que_epreuves_incluses = '' OR que_epreuves_incluses IS NULL) + "; + + return (int)$objDatabase->fxGetVar($sql) > 0; +} + +/** MSIN-4436 — Fragment ORDER BY pour une clé de tri. */ +function fxBibResolveSortClause($epr_id, $sortKey, $strLangue = 'fr', $eve_id = 0) { + global $objDatabase; + + $sortKey = fxBibNormalizeSortKey($sortKey); + if ($sortKey === '') { + return ''; + } + + if (preg_match('/^ba:(\d+)$/', $sortKey, $m)) { + $intBaId = (int)$m[1]; + $orderBy = trim((string)$objDatabase->fxGetVar( + "SELECT ba_sql_order FROM bib_assignements WHERE ba_id = $intBaId AND ba_actif = 1 LIMIT 1" + )); + return $orderBy !== '' ? $orderBy : ''; + } + + if (preg_match('/^que:(\d+)$/', $sortKey, $m)) { + $que_id = (int)$m[1]; + $epr_id = (int)$epr_id; + if ($epr_id <= 0) { + return ''; + } + if ($eve_id <= 0) { + $eve_id = (int)$objDatabase->fxGetVar( + "SELECT eve_id FROM inscriptions_epreuves WHERE epr_id = $epr_id LIMIT 1" + ); + } + if (!fxBibQuestionSortAllowedForEpreuve($epr_id, $que_id, $eve_id)) { + return ''; + } + $strCol = ($strLangue === 'en') ? 'que_choix_en' : 'que_choix_fr'; + return "( + SELECT TRIM(rq.$strCol) + FROM resultats_questions rq + WHERE rq.que_actif = 1 + AND rq.que_id = $que_id + AND rq.par_id = p.par_id + AND rq.pec_id = p.pec_id + LIMIT 1 + )"; + } + + return ''; +} + +/** MSIN-4436 — ORDER BY complet (2 niveaux + tie-breaker). */ +function fxBibBuildBatchOrderBy($epr_id, $sort1, $sort2 = '', $strLangue = 'fr', $eve_id = 0) { + $epr_id = (int)$epr_id; + if ($eve_id <= 0 && $epr_id > 0) { + global $objDatabase; + $eve_id = (int)$objDatabase->fxGetVar( + "SELECT eve_id FROM inscriptions_epreuves WHERE epr_id = $epr_id LIMIT 1" + ); + } + + $tabParts = []; + foreach ([$sort1, $sort2] as $mixKey) { + $strClause = fxBibResolveSortClause($epr_id, $mixKey, $strLangue, $eve_id); + if ($strClause !== '' && !in_array($strClause, $tabParts, true)) { + $tabParts[] = $strClause; + } + } + + if (empty($tabParts)) { + $tabParts[] = 'p.par_nom, p.par_prenom'; + } + + $tabParts[] = 'p.par_id ASC'; + + return implode(', ', $tabParts); +} + +/** MSIN-4436 — Rendu des '; + } + + $tabFixed = []; + $tabQuestions = []; + foreach ($tabOptions as $opt) { + if (($opt['group'] ?? '') === 'question') { + $tabQuestions[] = $opt; + } else { + $tabFixed[] = $opt; + } + } + + if (!empty($tabFixed)) { + $html .= ''; + foreach ($tabFixed as $opt) { + $strKey = fxBibEsc($opt['key'] ?? ''); + $html .= ''; + } + $html .= ''; + } + + if (!empty($tabQuestions)) { + $html .= ''; + foreach ($tabQuestions as $opt) { + $strKey = fxBibEsc($opt['key'] ?? ''); + $html .= ''; + } + $html .= ''; + } + + return $html; +} + // v2 — options d'ordre batch (table bib_assignements) -function renderBatchConfig($tabBibAssignments, $strLangue = 'fr', $isTeamEpreuve = false) { +function renderBatchConfig($epr_id, $eve_id, $strLangue = 'fr', $isTeamEpreuve = false, $strSort1 = '', $strSort2 = '') { + $epr_id = (int)$epr_id; + $eve_id = (int)$eve_id; + $strSort1 = fxBibNormalizeSortKey($strSort1); + $strSort2 = fxBibNormalizeSortKey($strSort2); + if ($strSort1 === '') { + $strSort1 = 'ba:2'; + } + + $tabOptions = fxBibGetSortOptionsForEpreuve($epr_id, $eve_id, $strLangue, $isTeamEpreuve, true); ob_start(); ?> @@ -2650,21 +2981,17 @@ function renderBatchConfig($tabBibAssignments, $strLangue = 'fr', $isTeamEpreuve
-
+
+ +
+ +
@@ -2997,6 +3324,10 @@ function fxBibStaticFallback($clef) { 'bib_v4_js_global_no_seq' => ['fr' => 'Cochez au moins une séquence pour les épreuves sélectionnées.', 'en' => 'Check at least one sequence for the selected races.'], 'bib_v4_global_batch_plan_invalid' => ['fr' => 'Sélection invalide : choisissez au moins une épreuve solo et des séquences.', 'en' => 'Invalid selection: pick at least one solo race and sequences.'], 'bib_v4_global_batch_need_two' => ['fr' => "L'assignation globale nécessite au moins deux épreuves solo avec des séquences disponibles.", 'en' => 'Global assignment requires at least two solo races with available sequences.'], + 'bib_v4_batch_order_2' => ['fr' => 'Tri secondaire', 'en' => 'Secondary sort'], + 'bib_v4_batch_order_none' => ['fr' => '— Aucun —', 'en' => '— None —'], + 'bib_v4_batch_sort_group_fixed' => ['fr' => 'Critères fixes', 'en' => 'Fixed criteria'], + 'bib_v4_batch_sort_group_questions'=> ['fr' => 'Questions', 'en' => 'Questions'], 'bib_v4_event_config_title' => ['fr' => "Configuration globale pour l'assignation de dossard", 'en' => 'Global bib assignment settings'], 'bib_v4_event_config_inter_epr_on' => ['fr' => 'Inter épreuve', 'en' => 'Inter-race'], 'bib_v4_qty_summary_title' => ['fr' => 'Sommaire des quantités', 'en' => 'Quantity summary'], @@ -3844,7 +4175,7 @@ function renderBibRangeHeader() { } /** Boutons assignation / reset / auto — fusionnés dans la boîte Assignation de dossard. */ -function renderBibAssignActions($tabBibAssignments, $strLangue, $infoBib, $blnAutoActive, $blnTeamEpreuve, $blnHasSequences = false) { +function renderBibAssignActions($epr_id, $eve_id, $strLangue, $infoBib, $blnAutoActive, $blnTeamEpreuve, $blnHasSequences = false, array $tabEpreuve = []) { $blnShowReset = $blnHasSequences && (int)$infoBib['avec_bib'] > 0; ob_start(); ?> @@ -3918,7 +4249,10 @@ function renderBibAssignActions($tabBibAssignments, $strLangue, $infoBib, $blnAu
- +
@@ -4178,15 +4512,17 @@ function renderBibGlobalBatchPanel($int_eve_id, $tabEpreuves, $tabBibAssignments
+
+ +
+ +
@@ -4387,12 +4723,14 @@ if ($teamMode <= 0) { 0 + count($tabBibStats) > 0, + $tabEpreuves[$i] ); ?>
@@ -6490,42 +6828,30 @@ echo $p['no_equipe']; fxGetVar($sqlOrder)); - - // ===================== - // SÉCURITÉ / FALLBACK - // Si aucun ordre valide, on garde un ordre stable. - // ===================== - if ($orderBy == '') { - $orderBy = "p.par_nom, p.par_prenom"; + if ($strSort1 === $strSort2) { + $strSort2 = ''; } + $orderBy = fxBibBuildBatchOrderBy($epr_id, $strSort1, $strSort2, $strLangue ?? 'fr'); + // ===================== // PARTICIPANTS À ASSIGNER (solo et équipe) // - jointure c.* requise pour les tris par nom d'équipe (pec_nom_equipe) diff --git a/php/inc_settings.php b/php/inc_settings.php index d010cdc..98a4873 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,7 +7,7 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.754'); +define('_VERSION_CODE', '4.72.755'); define('_DATE_CODE', '2026-07-08'); //MSIN-4290 define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe'); diff --git a/sql/MSIN-4424-bib-allow-inter-epr.sql b/sql/MSIN-4424-bib-allow-inter-epr.sql index a6f157b..8c4a467 100644 --- a/sql/MSIN-4424-bib-allow-inter-epr.sql +++ b/sql/MSIN-4424-bib-allow-inter-epr.sql @@ -33,6 +33,9 @@ INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_ SELECT 'bib_v4_anomalies_inter_epr_dupes_title', 'fr', 'Dossards en double entre épreuves', 'Même numéro de dossard utilisé sur plus d''une épreuve alors que l''option n''est pas activée.', 'compte.php', '', 0, 1, '', '', '', NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_inter_epr_dupes_title' AND info_langue = 'fr' AND info_prg = 'compte.php'); +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_anomalies_inter_epr_dupes_title', 'en', 'Duplicate bibs across races', 'Same bib number used on more than one race while the option is disabled.', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_inter_epr_dupes_title' AND info_langue = 'en' AND info_prg = 'compte.php'); INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) SELECT 'bib_v4_event_config_inter_epr_on', 'fr', 'Inter épreuve', 'Indique que les mêmes numéros de dossard sont autorisés entre épreuves.', 'compte.php', '', 0, 1, '', '', '', NOW() diff --git a/sql/MSIN-4436-bib-sort-levels.sql b/sql/MSIN-4436-bib-sort-levels.sql new file mode 100644 index 0000000..456e756 --- /dev/null +++ b/sql/MSIN-4436-bib-sort-levels.sql @@ -0,0 +1,72 @@ +-- MSIN-4436 — Tri batch dossards : genre, 2 niveaux, questions par épreuve +-- Idempotent. Exécuter avant déploiement PHP/JS. + +SET @db := DATABASE(); + +-- Mémorisation tri 1 / tri 2 par épreuve (clés ba:X ou que:Y) +SET @col_sort1 := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'inscriptions_epreuves' AND COLUMN_NAME = 'ba_bib_sort_1' +); +SET @sql_sort1 := IF( + @col_sort1 = 0, + 'ALTER TABLE inscriptions_epreuves ADD COLUMN ba_bib_sort_1 VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''MSIN-4436 — tri batch principal (ba:ID ou que:ID)'' AFTER ba_id', + 'SELECT ''ba_bib_sort_1 already exists'' AS note' +); +PREPARE stmt_sort1 FROM @sql_sort1; +EXECUTE stmt_sort1; +DEALLOCATE PREPARE stmt_sort1; + +SET @col_sort2 := ( + SELECT COUNT(*) FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db AND TABLE_NAME = 'inscriptions_epreuves' AND COLUMN_NAME = 'ba_bib_sort_2' +); +SET @sql_sort2 := IF( + @col_sort2 = 0, + 'ALTER TABLE inscriptions_epreuves ADD COLUMN ba_bib_sort_2 VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''MSIN-4436 — tri batch secondaire (optionnel)'' AFTER ba_bib_sort_1', + 'SELECT ''ba_bib_sort_2 already exists'' AS note' +); +PREPARE stmt_sort2 FROM @sql_sort2; +EXECUTE stmt_sort2; +DEALLOCATE PREPARE stmt_sort2; + +-- Phase 0 — Tri par genre (catalogue bib_assignements) +INSERT INTO bib_assignements (ba_nom_fr, ba_nom_en, ba_sql_order, ba_equipe, ba_tri, ba_actif) +SELECT 'Genre', 'Gender', 'FIELD(p.par_sexe, ''f'', ''h'', ''n'', ''a''), p.par_nom, p.par_prenom', 0, 55, 1 +FROM DUAL +WHERE NOT EXISTS ( + SELECT 1 FROM bib_assignements + WHERE ba_actif = 1 AND ba_sql_order LIKE '%par_sexe%' +); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_batch_order_2', 'fr', 'Tri secondaire', 'Critère optionnel pour départager les ex æquo du tri principal.', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_batch_order_2' AND info_langue = 'fr' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_batch_order_2', 'en', 'Secondary sort', 'Optional tie-breaker after the primary sort.', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_batch_order_2' AND info_langue = 'en' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_batch_order_none', 'fr', '— Aucun —', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_batch_order_none' AND info_langue = 'fr' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_batch_order_none', 'en', '— None —', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_batch_order_none' AND info_langue = 'en' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_batch_sort_group_fixed', 'fr', 'Critères fixes', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_batch_sort_group_fixed' AND info_langue = 'fr' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_batch_sort_group_fixed', 'en', 'Fixed criteria', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_batch_sort_group_fixed' AND info_langue = 'en' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_batch_sort_group_questions', 'fr', 'Questions', 'Réponses à une question d''inscription pour cette épreuve.', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_batch_sort_group_questions' AND info_langue = 'fr' AND info_prg = 'compte.php'); + +INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation) +SELECT 'bib_v4_batch_sort_group_questions', 'en', 'Questions', 'Registration question answers for this race.', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_batch_sort_group_questions' AND info_langue = 'en' AND info_prg = 'compte.php');