From e9752ddcb75bb9cbf12584b237b55df451c70c9d Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 16 Jul 2026 10:42:33 -0400 Subject: [PATCH] =?UTF-8?q?MSIN-4456=20=E2=80=94=20Enhance=20sorting=20fun?= =?UTF-8?q?ctionality=20for=20team=20names=20in=20bib=20assignments.=20Int?= =?UTF-8?q?roduce=20new=20functions=20to=20ensure=20reliable=20ordering=20?= =?UTF-8?q?by=20team=20name=20and=20implement=20PHP-based=20sorting=20for?= =?UTF-8?q?=20participants=20when=20the=20primary=20sort=20criterion=20is?= =?UTF-8?q?=20a=20team.=20Update=20AJAX=20responses=20to=20include=20addit?= =?UTF-8?q?ional=20sorting=20parameters.=20Increment=20version=20code=20to?= =?UTF-8?q?=204.72.833.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ajax_bib_range.php | 11 +- js/v2/bib-assignments.js | 13 +- php/inc_fx_promoteur.php | 182 ++++++++++++++++++++++++++- php/inc_settings.php | 2 +- sql/MSIN-4456-bib-team-name-sort.sql | 16 +++ 5 files changed, 213 insertions(+), 11 deletions(-) create mode 100644 sql/MSIN-4456-bib-team-name-sort.sql diff --git a/ajax_bib_range.php b/ajax_bib_range.php index 092cf6e..4b7f872 100644 --- a/ajax_bib_range.php +++ b/ajax_bib_range.php @@ -941,7 +941,10 @@ if ($action == 'batch_go') { echo json_encode([ 'success' => true, - 'html' => $html + 'html' => $html, + // MSIN-4456 — diagnostic temporaire (Network) : vérifier sort_1=ba:5 pour Nom d'équipe + 'sort_1' => $strSort1, + 'sort_2' => $strSort2, ]); exit; @@ -1271,6 +1274,9 @@ if ($action == 'reset_apply') { $count++; } + // MSIN-4453 — Effacer aussi no_equipe (sinon le team# original reste et est réutilisé) + fxClearTeamNumbersAfterBibReset($epr_id, $participants); + $html = renderBibView( $epr_id, 0, @@ -1337,6 +1343,9 @@ if ($action == 'reset_all_apply') { $count++; } + // MSIN-4453 — Effacer aussi no_equipe (aligné reset legacy) + fxClearTeamNumbersAfterBibReset($epr_id); + $html = renderBibView( $epr_id, 0, diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js index 229cd81..06f458b 100644 --- a/js/v2/bib-assignments.js +++ b/js/v2/bib-assignments.js @@ -205,11 +205,16 @@ if (!row) { return { sort1: '', sort2: '' }; } - var s1 = row.querySelector('.batch-order'); - var s2 = row.querySelector('.batch-order-2'); + // MSIN-4456 — Lire explicitement primaire vs secondaire (évite toute ambiguïté de sélecteur). + var wrap1 = row.querySelector('.epr-batch-order-wrap:not(.epr-batch-order-wrap--secondary)'); + var wrap2 = row.querySelector('.epr-batch-order-wrap--secondary'); + var s1 = (wrap1 && wrap1.querySelector('select.batch-order')) + || row.querySelector('select.batch-order'); + var s2 = (wrap2 && wrap2.querySelector('select.batch-order-2')) + || row.querySelector('select.batch-order-2'); return { - sort1: s1 ? s1.value : '', - sort2: s2 ? s2.value : '' + sort1: s1 ? String(s1.value || '').trim() : '', + sort2: s2 ? String(s2.value || '').trim() : '' }; } diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index a12c8a8..abbd72c 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -2934,6 +2934,85 @@ function fxBibQuestionSortAllowedForEpreuve($epr_id, $que_id, $eve_id) { return (int)$objDatabase->fxGetVar($sql) > 0; } +/** + * MSIN-4456 — Clause ORDER BY fiable pour « Nom d'équipe ». + * Regroupe d'abord par nom (pec / repli par_nom_equipe), puis pec_id (même nom ≠ même équipe). + */ +function fxBibTeamNameOrderClause() { + return "TRIM(IFNULL(NULLIF(c.pec_nom_equipe, ''), IFNULL(p.par_nom_equipe, ''))), p.pec_id"; +} + +/** MSIN-4456 — La clé de tri est-elle un critère ba_equipe (ex. Nom d'équipe) ? */ +function fxBibSortKeyIsTeamCriterion($sortKey) { + global $objDatabase; + + $sortKey = fxBibNormalizeSortKey($sortKey); + if (!preg_match('/^ba:(\d+)$/', $sortKey, $m)) { + return false; + } + + return (int)$objDatabase->fxGetVar( + "SELECT ba_equipe FROM bib_assignements WHERE ba_id = " . (int)$m[1] . " AND ba_actif = 1 LIMIT 1" + ) === 1; +} + +/** + * MSIN-4456 — Re-tri PHP des participants (filet si l'ORDER BY SQL ne regroupe pas). + * Appliqué seulement quand le tri principal est un critère équipe. + */ +function fxBibSortParticipantsByTeamName(array $participants) { + if (empty($participants)) { + return $participants; + } + + $rows = []; + foreach ($participants as $row) { + $rows[] = $row; + } + + usort($rows, function ($a, $b) { + $strTeamA = trim((string)($a['pec_nom_equipe'] ?? '')); + if ($strTeamA === '') { + $strTeamA = trim((string)($a['par_nom_equipe'] ?? '')); + } + $strTeamB = trim((string)($b['pec_nom_equipe'] ?? '')); + if ($strTeamB === '') { + $strTeamB = trim((string)($b['par_nom_equipe'] ?? '')); + } + + $intCmp = strcasecmp($strTeamA, $strTeamB); + if ($intCmp !== 0) { + return $intCmp; + } + + $intPecA = (int)($a['pec_id_original'] ?? $a['pec_id'] ?? 0); + $intPecB = (int)($b['pec_id_original'] ?? $b['pec_id'] ?? 0); + if ($intPecA !== $intPecB) { + return $intPecA <=> $intPecB; + } + + $intCmp = strcasecmp((string)($a['par_nom'] ?? ''), (string)($b['par_nom'] ?? '')); + if ($intCmp !== 0) { + return $intCmp; + } + + $intCmp = strcasecmp((string)($a['par_prenom'] ?? ''), (string)($b['par_prenom'] ?? '')); + if ($intCmp !== 0) { + return $intCmp; + } + + return ((int)($a['par_id'] ?? 0)) <=> ((int)($b['par_id'] ?? 0)); + }); + + $out = []; + $intCtr = 1; + foreach ($rows as $row) { + $out[$intCtr++] = $row; + } + + return $out; +} + /** MSIN-4436 — Fragment ORDER BY pour une clé de tri. */ function fxBibResolveSortClause($epr_id, $sortKey, $strLangue = 'fr', $eve_id = 0) { global $objDatabase; @@ -2945,9 +3024,17 @@ function fxBibResolveSortClause($epr_id, $sortKey, $strLangue = 'fr', $eve_id = 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" - )); + $tabBa = $objDatabase->fxGetRow( + "SELECT ba_sql_order, ba_equipe FROM bib_assignements WHERE ba_id = $intBaId AND ba_actif = 1 LIMIT 1" + ); + if (!$tabBa) { + return ''; + } + // MSIN-4456 — Ne pas se fier seul à ba_sql_order historique (souvent déjà + nom/prénom). + if ((int)($tabBa['ba_equipe'] ?? 0) === 1) { + return fxBibTeamNameOrderClause(); + } + $orderBy = trim((string)($tabBa['ba_sql_order'] ?? '')); return $orderBy !== '' ? $orderBy : ''; } @@ -2990,9 +3077,23 @@ function fxBibBuildBatchOrderBy($epr_id, $sort1, $sort2 = '', $strLangue = 'fr', ); } + $strSort1 = fxBibNormalizeSortKey($sort1); + $strSort2 = fxBibNormalizeSortKey($sort2); + if ($strSort1 === $strSort2) { + $strSort2 = ''; + } + $tabParts = []; - foreach ([$sort1, $sort2] as $mixKey) { + foreach ([$strSort1, $strSort2] as $mixKey) { + if ($mixKey === '') { + continue; + } $strClause = fxBibResolveSortClause($epr_id, $mixKey, $strLangue, $eve_id); + // MSIN-4456 — Tri principal « nom d'équipe » + secondaire alphabétique : + // la clause équipe ne doit PAS déjà embarquer par_nom (sinon le 2e niveau est noyé). + if ($mixKey === $strSort1 && fxBibSortKeyIsTeamCriterion($mixKey) && $strSort2 !== '') { + $strClause = fxBibTeamNameOrderClause(); + } if ($strClause !== '' && !in_array($strClause, $tabParts, true)) { $tabParts[] = $strClause; } @@ -7590,7 +7691,18 @@ $sql = " ORDER BY $orderBy "; - return $objDatabase->fxGetResults($sql); + $tabParticipants = $objDatabase->fxGetResults($sql); + if (!is_array($tabParticipants) || empty($tabParticipants)) { + return is_array($tabParticipants) ? $tabParticipants : []; + } + + // MSIN-4456 — Filet : si tri principal = nom d'équipe, forcer le regroupement en PHP + // (dossards individuels comme 1 équipe = 1 dossard). + if (fxBibSortKeyIsTeamCriterion($strSort1)) { + $tabParticipants = fxBibSortParticipantsByTeamName($tabParticipants); + } + + return $tabParticipants; } function fxGetRangesForBatchAssign($epr_id, $ranges) { global $objDatabase; @@ -8202,6 +8314,66 @@ function fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id = 'nb_manque' => $nbManque ]; } +/** + * MSIN-4453 — Efface no_equipe après réinitialisation des dossards (aligné sur le reset legacy). + * Sans $tabParticipants : toute l'épreuve. Avec liste (reset par séquences) : uniquement les équipes + * dont plus aucun membre actif n'a de dossard. + */ +function fxClearTeamNumbersAfterBibReset($epr_id, $tabParticipants = null) { + global $objDatabase; + + $epr_id = (int)$epr_id; + + if ($epr_id <= 0) { + return; + } + + if ($tabParticipants === null) { + $sql = " + UPDATE resultats_epreuves_commandees + SET no_equipe = NULL + WHERE epr_id = $epr_id + "; + $objDatabase->fxQuery($sql); + return; + } + + $teamKeys = []; + + foreach ($tabParticipants as $p) { + $teamKey = fxGetBatchTeamKey($p); + + if ($teamKey > 0) { + $teamKeys[$teamKey] = true; + } + } + + if (empty($teamKeys)) { + return; + } + + $ids = implode(',', array_map('intval', array_keys($teamKeys))); + $sqlBibNum = fxBibSqlNoBibUnsigned('p.no_bib'); + + $sql = " + UPDATE resultats_epreuves_commandees c + SET c.no_equipe = NULL + WHERE c.epr_id = $epr_id + AND c.pec_id_original IN ($ids) + AND NOT EXISTS ( + SELECT 1 + FROM resultats_participants p + WHERE p.pec_id = c.pec_id_original + AND p.epr_id = c.epr_id + AND p.is_cancelled = 0 + AND p.no_bib IS NOT NULL + AND $sqlBibNum > 0 + ) + "; + + $objDatabase->fxQuery($sql); +} + /** MSIN-4436 — Tous les participants avec dossard sur l'épreuve (y compris hors séquences). */ function fxGetParticipantsForEpreuveResetAll($epr_id) { global $objDatabase; diff --git a/php/inc_settings.php b/php/inc_settings.php index 5b280cc..db4e2a1 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,7 +7,7 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.832'); +define('_VERSION_CODE', '4.72.833'); define('_DATE_CODE', '2026-07-16'); //MSIN-4290 define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe'); diff --git a/sql/MSIN-4456-bib-team-name-sort.sql b/sql/MSIN-4456-bib-team-name-sort.sql new file mode 100644 index 0000000..ea56aad --- /dev/null +++ b/sql/MSIN-4456-bib-team-name-sort.sql @@ -0,0 +1,16 @@ +-- MSIN-4456 — Tri batch « Nom d'équipe » : clause SQL sans nom/prénom embarqués +-- Contexte : en mode dossards individuels, le ba_sql_order historique +-- TRIM(c.pec_nom_equipe), p.par_nom, p.par_prenom +-- noyait le tri secondaire et pouvait faire paraître un ordre purement alphabétique. +-- Notes : idempotent. Le PHP force aussi cette clause pour ba_equipe = 1. + +UPDATE bib_assignements +SET ba_sql_order = 'TRIM(IFNULL(NULLIF(c.pec_nom_equipe, ''''), IFNULL(p.par_nom_equipe, ''''))), p.pec_id' +WHERE ba_actif = 1 + AND ba_equipe = 1 + AND ( + ba_sql_order LIKE '%pec_nom_equipe%' + OR ba_nom_fr LIKE '%équipe%' + OR ba_nom_fr LIKE '%equipe%' + OR ba_nom_en LIKE '%team%' + );