From 70f4dac1777d944e17ab0a7faea365823c5dfa01 Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 8 Jul 2026 10:17:15 -0400 Subject: [PATCH] MSIN-4424 Implement inter-epreuve duplicate handling and update version code to 4.72.750 This commit introduces functionality to allow the same bib numbers across different epreuves, enhancing the flexibility of event management. A new AJAX action, `save_allow_inter_epr`, is added to manage this setting, along with corresponding JavaScript to handle user interactions. The database functions are updated to support this feature, ensuring proper validation and anomaly reporting. The version code is incremented to reflect these changes. --- ajax_bib_range.php | 38 +++- ajax_inscr_gestion.php | 6 +- css/style.css | 33 ++++ js/v2/bib-assignments.js | 43 +++++ php/inc_fx_inscriptions_gestion.php | 24 ++- php/inc_fx_promoteur.php | 248 ++++++++++++++++++++++++-- php/inc_settings.php | 2 +- sql/MSIN-4424-bib-allow-inter-epr.sql | 38 ++++ 8 files changed, 413 insertions(+), 19 deletions(-) create mode 100644 sql/MSIN-4424-bib-allow-inter-epr.sql diff --git a/ajax_bib_range.php b/ajax_bib_range.php index 78bc912..5997e3f 100644 --- a/ajax_bib_range.php +++ b/ajax_bib_range.php @@ -35,6 +35,7 @@ $arrBibAjaxActions = [ 'reset_preview', 'reset_apply', 'reset_all_apply', + 'save_allow_inter_epr', 'save_team_mode', 'toggle_lock', 'save_auto_config', @@ -257,7 +258,7 @@ if ($action == 'save') { // ===================== // VALIDATION 4 — OVERLAP pas de double epreuves // ===================== - $tabOverlaps = fxBibGetOverlappingRanges($eve_id, $start, $end, $is_new ? 0 : $id); + $tabOverlaps = fxBibGetOverlappingRanges($eve_id, $start, $end, $is_new ? 0 : $id, $epr_id); if (!empty($tabOverlaps)) { echo json_encode([ @@ -356,6 +357,41 @@ if ($action == 'batch_analysis') { exit; } +// ===================== +// MSIN-4424 — Réglage doublons inter-épreuves (événement) +// ===================== +if ($action == 'save_allow_inter_epr') { + + $int_eve_id = (int)($_POST['eve_id'] ?? 0); + $blnAllow = !empty($_POST['allow']) && (string)$_POST['allow'] !== '0'; + + if ($int_eve_id <= 0) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_ajax_epr_invalid'), + ]); + exit; + } + + $result = fxBibSetEventAllowsInterEprDuplicates($int_eve_id, $blnAllow); + + if (empty($result['success'])) { + echo json_encode($result); + exit; + } + + $strLangueAnom = $_SESSION['lang'] ?? 'fr'; + $tabAnomalies = fxBibCollectAnomalies($int_eve_id, $strLangueAnom); + + echo json_encode([ + 'success' => true, + 'allow_inter_epr' => !empty($result['allow_inter_epr']) ? 1 : 0, + 'anomalies_html' => renderBibAnomaliesPanel($int_eve_id, $strLangueAnom, $tabAnomalies), + 'anomaly_count' => fxBibAnomalyTotalCount($tabAnomalies), + ]); + exit; +} + // MSIN-4436 — Panneau assignation globale (chargement différé à l'ouverture). if ($action == 'global_batch_panel') { diff --git a/ajax_inscr_gestion.php b/ajax_inscr_gestion.php index db51a7c..a551c85 100644 --- a/ajax_inscr_gestion.php +++ b/ajax_inscr_gestion.php @@ -147,7 +147,11 @@ switch ($strAction) { . ' WHERE p.par_id = ' . $intParId . ' AND p.pec_id = e.pec_id_original LIMIT 1' )); - $arrDup = fxInscrGestionFindBibDuplicates($intEveId, $strNoBib, $intPecOrig, $blnTeam); + $intCurrentEprId = (int)$objDatabase->fxGetVar( + 'SELECT epr_id FROM resultats_participants WHERE par_id = ' . $intParId . ' LIMIT 1' + ); + + $arrDup = fxInscrGestionFindBibDuplicates($intEveId, $strNoBib, $intPecOrig, $blnTeam, $intCurrentEprId); $arrWho = array(); foreach ($arrDup as $rowDup) { $arrWho[] = array( diff --git a/css/style.css b/css/style.css index b1aaac5..3046aad 100644 --- a/css/style.css +++ b/css/style.css @@ -1064,6 +1064,39 @@ ul.ms1-menu-compte li a:hover, ul.ms1-menu-compte li a:active { line-height:1.45; } +/* MSIN-4424 — Bandeau réglages dossards (haut du module). */ +.bib-event-settings{ + border:1px solid #c8d6e5; + border-radius:6px; + background:#f8fafc; + padding:10px 14px; + margin-bottom:4px; +} + +.bib-event-settings__label{ + display:flex; + align-items:flex-start; + gap:8px; + margin:0; + font-weight:600; + font-size:14px; + line-height:1.4; + cursor:pointer; +} + +.bib-event-settings__label input[type="checkbox"]{ + margin-top:3px; + flex-shrink:0; +} + +.bib-event-settings__text{ + flex:1 1 auto; +} + +.bib-anomaly-block--inter-epr-dupes .bib-anomaly-block-title{ + color:#8a3b12; +} + /* MSIN-4436 — Assignation globale (épreuves solo) */ .bib-global-batch{ border:1px solid #c8d6e5; diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js index b5746f7..9b2912f 100644 --- a/js/v2/bib-assignments.js +++ b/js/v2/bib-assignments.js @@ -2711,6 +2711,49 @@ }); }); + // MSIN-4424 — Autoriser mêmes numéros entre épreuves (réglage événement). + let chkAllowInterEpr = moduleBib.querySelector('.bib-event-allow-inter-epr'); + if (chkAllowInterEpr) { + chkAllowInterEpr.addEventListener('change', function () { + let eveId = moduleBib.dataset.eveId || ''; + if (!eveId) { + return; + } + + bibFetch('/ajax_bib_range.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: + 'action=save_allow_inter_epr' + + '&eve_id=' + encodeURIComponent(eveId) + + '&allow=' + (chkAllowInterEpr.checked ? '1' : '0') + + bibAjaxLangSuffix() + }) + .then(function (res) { return res.json(); }) + .then(function (data) { + if (!data.success) { + chkAllowInterEpr.checked = !chkAllowInterEpr.checked; + alert(data.message || bibJs('jsErrGeneric')); + return; + } + + moduleBib.dataset.allowInterEpr = data.allow_inter_epr ? '1' : '0'; + + let mount = document.getElementById('bib-anomalies-mount'); + if (mount && data.anomalies_html) { + mount.innerHTML = data.anomalies_html; + initModuleBibTippy(mount); + } + }) + .catch(function () { + chkAllowInterEpr.checked = !chkAllowInterEpr.checked; + alert(bibJs('jsErrGeneric')); + }); + }); + } + // MSIN-4436 — Anomalies : coquille légère au chargement, détail en AJAX. if (document.querySelector('#bib-anomalies-panel[data-bib-anomalies-deferred="1"]')) { refreshBibAnomaliesPanel(); diff --git a/php/inc_fx_inscriptions_gestion.php b/php/inc_fx_inscriptions_gestion.php index 6e19fae..8dd6a97 100644 --- a/php/inc_fx_inscriptions_gestion.php +++ b/php/inc_fx_inscriptions_gestion.php @@ -619,10 +619,12 @@ function fxInscrGestionBibDuplicatePolicy($intEveId) { * @param bool $blnTeam true = dossard d'equipe (e.no_equipe), false = individuel (p.no_bib) * @return array liste de lignes ['par_nom','par_prenom','epr_id'] */ -function fxInscrGestionFindBibDuplicates($intEveId, $strNoBib, $intExceptPec, $blnTeam) { +function fxInscrGestionFindBibDuplicates($intEveId, $strNoBib, $intExceptPec, $blnTeam, $intCurrentEprId = 0) { global $objDatabase; $strNoBib = trim((string)$strNoBib); + $intCurrentEprId = (int)$intCurrentEprId; + if (intval($intEveId) <= 0 || $strNoBib === '') { return array(); } @@ -639,7 +641,25 @@ function fxInscrGestionFindBibDuplicates($intEveId, $strNoBib, $intExceptPec, $b . ' ORDER BY p.par_nom, p.par_prenom'; $tab = $objDatabase->fxGetResults($sql); - return is_array($tab) ? $tab : array(); + if (!is_array($tab) || empty($tab)) { + return array(); + } + + $blnAllowInter = function_exists('fxBibEventAllowsInterEprDuplicates') + && fxBibEventAllowsInterEprDuplicates($intEveId); + + $arrOut = array(); + foreach ($tab as $rowDup) { + $intDupEprId = (int)($rowDup['epr_id'] ?? 0); + + if ($intCurrentEprId > 0 && $intDupEprId !== $intCurrentEprId && $blnAllowInter) { + continue; + } + + $arrOut[] = $rowDup; + } + + return $arrOut; } function fxInscrGestionEpreuveLabel($intEprId, $strLangue) { diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index 3557c9a..730b808 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -1539,8 +1539,27 @@ function fxShowDupes($int_eve_id) { $strPage = '/account'; } - $sqlNoBib = "SELECT no_bib, epr_id, COUNT(*) c FROM resultats_participants where eve_id = " . intval($int_eve_id) . " AND no_bib <> '' GROUP BY no_bib HAVING c > 1"; - $tabNoBib = $objDatabase->fxGetResults($sqlNoBib); + $sqlNoBib = "SELECT no_bib, epr_id, COUNT(*) c FROM resultats_participants where eve_id = " . intval($int_eve_id) . " AND no_bib <> '' AND is_cancelled = 0 GROUP BY no_bib, epr_id HAVING c > 1"; + $tabIntraDupes = $objDatabase->fxGetResults($sqlNoBib); + + $tabInterDupes = []; + if (!fxBibEventAllowsInterEprDuplicates((int)$int_eve_id)) { + $sqlInter = "SELECT no_bib FROM resultats_participants WHERE eve_id = " . intval($int_eve_id) + . " AND no_bib <> '' AND is_cancelled = 0 GROUP BY no_bib HAVING COUNT(DISTINCT epr_id) > 1"; + $tabInterDupes = $objDatabase->fxGetResults($sqlInter); + } + + $tabBibKeys = []; + if (is_array($tabIntraDupes)) { + foreach ($tabIntraDupes as $row) { + $tabBibKeys[(int)$row['no_bib']] = true; + } + } + if (is_array($tabInterDupes)) { + foreach ($tabInterDupes as $row) { + $tabBibKeys[(int)$row['no_bib']] = true; + } + } ?>
@@ -1559,11 +1578,11 @@ function fxShowDupes($int_eve_id) { fxGetResults($sqlDupes); if ($tabDupes != null) { @@ -1571,7 +1590,7 @@ function fxShowDupes($int_eve_id) { $tabEpreuve = fxGetEpreuve($dupe['epr_id']); ?> - + true]; } +/** MSIN-4424 — Mêmes numéros de dossard autorisés entre épreuves (réglage événement). */ +function fxBibEventAllowsInterEprDuplicates($int_eve_id) { + global $objDatabase; + + static $memCache = []; + + $int_eve_id = (int)$int_eve_id; + + if ($int_eve_id <= 0) { + return false; + } + + if (array_key_exists($int_eve_id, $memCache)) { + return $memCache[$int_eve_id]; + } + + $intVal = (int)$objDatabase->fxGetVar( + 'SELECT eve_bib_allow_inter_epr FROM inscriptions_evenements WHERE eve_id = ' . $int_eve_id . ' LIMIT 1' + ); + + $memCache[$int_eve_id] = ($intVal === 1); + + return $memCache[$int_eve_id]; +} + +/** MSIN-4424 — Persiste le réglage doublons inter-épreuves. */ +function fxBibSetEventAllowsInterEprDuplicates($int_eve_id, $blnAllow) { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + $intAllow = $blnAllow ? 1 : 0; + + if ($int_eve_id <= 0) { + return ['success' => false, 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')]; + } + + $objDatabase->fxQuery( + 'UPDATE inscriptions_evenements SET eve_bib_allow_inter_epr = ' . $intAllow + . ' WHERE eve_id = ' . $int_eve_id . ' LIMIT 1' + ); + + return ['success' => true, 'allow_inter_epr' => ($intAllow === 1)]; +} + /** Resout eve_id depuis la requete AJAX bib (eve_id direct ou via epr_id). */ function fxBibResolveEveIdFromRequest() { global $objDatabase; @@ -3034,22 +3097,30 @@ function fxBibEpreuveDisplayName(array $row, $strLangue = 'fr') { } /** - * Séquences de dossards qui chevauchent [start, end] sur le même événement. + * MSIN-4424 — Séquences qui chevauchent [start, end]. + * Par défaut : tout l'événement. Si doublons inter-épreuves autorisés : même epr_id seulement. + * * @return array|null Tableau 1-based fxGetResults, ou null si paramètres invalides. */ -function fxBibGetOverlappingRanges($eve_id, $start, $end, $exclude_bib_id = 0) { +function fxBibGetOverlappingRanges($eve_id, $start, $end, $exclude_bib_id = 0, $epr_id = 0) { global $objDatabase; $eve_id = (int)$eve_id; $start = (int)$start; $end = (int)$end; $exclude_bib_id = (int)$exclude_bib_id; + $epr_id = (int)$epr_id; if ($eve_id <= 0 || $start <= 0 || $end <= 0) { return null; } $sqlExclude = ($exclude_bib_id > 0) ? "AND b.epr_bib_id <> $exclude_bib_id" : ''; + $sqlEprOnly = ''; + + if ($epr_id > 0 && fxBibEventAllowsInterEprDuplicates($eve_id)) { + $sqlEprOnly = "AND b.epr_id = $epr_id"; + } $sql = " SELECT @@ -3070,6 +3141,7 @@ function fxBibGetOverlappingRanges($eve_id, $start, $end, $exclude_bib_id = 0) { AND b.epr_bib_start IS NOT NULL AND b.epr_bib_finish IS NOT NULL $sqlExclude + $sqlEprOnly AND ($start <= b.epr_bib_finish AND $end >= b.epr_bib_start) ORDER BY b.epr_bib_start, b.epr_bib_id "; @@ -4152,7 +4224,9 @@ function fxShowBibTool4($str_code, $int_eve_id, $strLangue){ ?>
-
> +
> + +
@@ -4574,6 +4648,101 @@ function fxBibAnomalyGroupDuplicateRows(array $rows, $strLangue = 'fr') { return array_values($grouped); } +/** + * MSIN-4424 — Dossards en double entre épreuves (même no_bib, epr_id distincts). + * Vide si le réglage événement autorise les doublons inter-épreuves. + * + * @return array> + */ +function fxBibAnomalyInterEprDuplicateBibs($int_eve_id, $strLangue = 'fr') { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + + if ($int_eve_id <= 0 || fxBibEventAllowsInterEprDuplicates($int_eve_id)) { + return []; + } + + $sqlBibNumP = fxBibSqlNoBibUnsigned('p.no_bib'); + $sqlBibNumP2 = fxBibSqlNoBibUnsigned('p2.no_bib'); + + $sql = " + SELECT + $sqlBibNumP AS no_bib, + p.epr_id, + p.par_id, + p.par_prenom, + p.par_nom, + e.epr_nom_fr, + e.epr_nom_en, + e.epr_type_fr, + e.epr_type_en, + e.epr_categorie_fr, + e.epr_categorie_en, + e.epr_actif + FROM resultats_participants p + INNER JOIN inscriptions_epreuves e ON e.epr_id = p.epr_id + WHERE e.eve_id = $int_eve_id + AND p.is_cancelled = 0 + AND $sqlBibNumP > 0 + AND $sqlBibNumP IN ( + SELECT $sqlBibNumP2 + FROM resultats_participants p2 + INNER JOIN inscriptions_epreuves e2 ON e2.epr_id = p2.epr_id + WHERE e2.eve_id = $int_eve_id + AND p2.is_cancelled = 0 + AND $sqlBibNumP2 > 0 + GROUP BY $sqlBibNumP2 + HAVING COUNT(DISTINCT p2.epr_id) > 1 + ) + ORDER BY $sqlBibNumP, p.epr_id, p.par_nom, p.par_prenom + "; + + $rows = $objDatabase->fxGetResults($sql); + + if (!is_array($rows) || empty($rows)) { + return []; + } + + $grouped = []; + + foreach ($rows as $row) { + $intBib = (int)$row['no_bib']; + $key = (string)$intBib; + + if (!isset($grouped[$key])) { + $grouped[$key] = [ + 'no_bib' => $intBib, + 'entries' => [], + ]; + } + + $intEprId = (int)$row['epr_id']; + $strEprKey = (string)$intEprId; + + if (!isset($grouped[$key]['entries'][$strEprKey])) { + $grouped[$key]['entries'][$strEprKey] = [ + 'epr_id' => $intEprId, + 'epr_label' => fxBibAnomalyEprLabel($row, $strLangue), + 'participants' => [], + ]; + } + + $grouped[$key]['entries'][$strEprKey]['participants'][] = [ + 'par_id' => (int)$row['par_id'], + 'par_prenom' => trim($row['par_prenom'] ?? ''), + 'par_nom' => trim($row['par_nom'] ?? ''), + ]; + } + + foreach ($grouped as &$item) { + $item['entries'] = array_values($item['entries']); + } + unset($item); + + return array_values($grouped); +} + /** * MSIN-4379 — Dossards en double (même epr_id, même no_bib, >1 unité active). * Couvre les séquences configurées et les épreuves sans séquence (saisie manuelle). @@ -4925,16 +5094,17 @@ function fxBibAnomalyOutOfRangeBibs($int_eve_id, $strLangue = 'fr') { /** MSIN-4379 — Agrège toutes les anomalies dossards pour un événement. */ function fxBibCollectAnomalies($int_eve_id, $strLangue = 'fr') { return [ - 'dupes' => fxBibAnomalyDuplicateBibs($int_eve_id, $strLangue), - 'out_of_range' => fxBibAnomalyOutOfRangeBibs($int_eve_id, $strLangue), - 'orphans' => fxBibAnomalyOrphanBibs($int_eve_id, $strLangue), - 'auto_miss' => fxBibAnomalyAutoMissBibs($int_eve_id, $strLangue), + 'dupes' => fxBibAnomalyDuplicateBibs($int_eve_id, $strLangue), + 'inter_epr_dupes' => fxBibAnomalyInterEprDuplicateBibs($int_eve_id, $strLangue), + 'out_of_range' => fxBibAnomalyOutOfRangeBibs($int_eve_id, $strLangue), + 'orphans' => fxBibAnomalyOrphanBibs($int_eve_id, $strLangue), + 'auto_miss' => fxBibAnomalyAutoMissBibs($int_eve_id, $strLangue), ]; } function fxBibAnomalyTotalCount(array $tabAnomalies) { $intTotal = 0; - foreach (['dupes', 'out_of_range', 'orphans', 'auto_miss'] as $strKey) { + foreach (['dupes', 'inter_epr_dupes', 'out_of_range', 'orphans', 'auto_miss'] as $strKey) { if (!empty($tabAnomalies[$strKey]) && is_array($tabAnomalies[$strKey])) { $intTotal += count($tabAnomalies[$strKey]); } @@ -4942,6 +5112,27 @@ function fxBibAnomalyTotalCount(array $tabAnomalies) { return $intTotal; } +/** MSIN-4424 — Bandeau réglages dossards (haut du module, au-dessus des épreuves). */ +function renderBibEventSettingsBanner($int_eve_id, $strLangue = 'fr') { + $int_eve_id = (int)$int_eve_id; + $blnAllow = fxBibEventAllowsInterEprDuplicates($int_eve_id); + + ob_start(); + ?> +
+ +
+ + +
+

+ + + +

+
    + +
  • + +
      + +
    • + +
        + +
      • + +
      +
    • + +
    +
  • + +
+
+ +

diff --git a/php/inc_settings.php b/php/inc_settings.php index bf6e538..eeb8180 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,7 +7,7 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.749'); +define('_VERSION_CODE', '4.72.750'); 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 new file mode 100644 index 0000000..5cc3dab --- /dev/null +++ b/sql/MSIN-4424-bib-allow-inter-epr.sql @@ -0,0 +1,38 @@ +-- MSIN-4424 — Autoriser les mêmes numéros de dossard entre épreuves (réglage événement) +-- Idempotent. Exécuter avant déploiement PHP/JS. + +SET @db := DATABASE(); + +SET @col_exists := ( + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = @db + AND TABLE_NAME = 'inscriptions_evenements' + AND COLUMN_NAME = 'eve_bib_allow_inter_epr' +); + +SET @sql_col := IF( + @col_exists = 0, + 'ALTER TABLE inscriptions_evenements ADD COLUMN eve_bib_allow_inter_epr TINYINT(1) UNSIGNED NOT NULL DEFAULT 0 COMMENT ''MSIN-4424 — 1 = mêmes no_bib autorisés entre épreuves'' AFTER eve_epreuve_modifiable', + 'SELECT ''eve_bib_allow_inter_epr already exists'' AS note' +); + +PREPARE stmt_col FROM @sql_col; +EXECUTE stmt_col; +DEALLOCATE PREPARE stmt_col; + +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_allow_inter_epr', 'fr', 'Autoriser les mêmes numéros de dossard entre épreuves', 'Permet d''utiliser les mêmes numéros sur plusieurs épreuves (ex. 5 km et 10 km). Les doublons à l''intérieur d''une même épreuve restent interdits.', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_allow_inter_epr' 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_allow_inter_epr', 'en', 'Allow the same bib numbers across races', 'Lets multiple races share the same bib numbers (e.g. 5K and 10K). Duplicates within a single race remain forbidden.', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_allow_inter_epr' 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_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');