diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index f1b7e89..a87beed 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -2897,6 +2897,7 @@ function fxBibStaticFallback($clef) { 'bib_v4_err_dupes' => ['fr' => 'Dossards en double :', 'en' => 'Duplicate bibs:'], 'bib_v4_anomalies_auto_miss_title' => ['fr' => 'Inscriptions sans dossard (auto)', 'en' => 'Registrations without a bib (auto)'], 'bib_v4_anomalies_out_of_range_title' => ['fr' => 'Dossards hors séquence', 'en' => 'Bibs outside sequences'], + 'bib_v4_anomalies_no_range' => ['fr' => 'Aucune séquence configurée', 'en' => 'No sequence configured'], 'bib_v4_anomalies_order_fmt' => ['fr' => 'Commande %s', 'en' => 'Order %s'], 'bib_v4_anomalies_assign_link' => ['fr' => 'Assigner un dossard', 'en' => 'Assign a bib'], 'bib_v4_js_loader_wait' => ['fr' => 'Chargement...', 'en' => 'Loading...'], @@ -4121,8 +4122,52 @@ function fxBibAnomalyEprLabel(array $tabEpreuve, $strLangue = 'fr') { return $str; } +/** SQL — l'épreuve a au moins une séquence de dossards configurée. */ +function fxBibSqlEpreuveHasBibRanges($strEprIdExpr) { + return "EXISTS ( + SELECT 1 + FROM inscriptions_epreuves_bib b0 + WHERE b0.epr_id = ($strEprIdExpr) + AND b0.epr_bib_start IS NOT NULL + AND b0.epr_bib_finish IS NOT NULL + )"; +} + /** - * MSIN-4379 — Dossards en double dans une séquence (même epr_id, même no_bib, >1 participant actif). + * MSIN-4379 — Regroupe les lignes SQL de fxBibAnomalyDuplicateBibs par séquence + no_bib. + * + * @return array> + */ +function fxBibAnomalyGroupDuplicateRows(array $rows, $strLangue = 'fr') { + $grouped = []; + foreach ($rows as $row) { + $intRangeId = (int)$row['epr_bib_id']; + $key = ($intRangeId > 0 ? $intRangeId : (int)$row['epr_id']) . '-' . (int)$row['no_bib']; + if (!isset($grouped[$key])) { + $grouped[$key] = [ + 'epr_id' => (int)$row['epr_id'], + 'epr_label' => fxBibAnomalyEprLabel($row, $strLangue), + 'range_id' => $intRangeId, + 'range_start' => (int)$row['epr_bib_start'], + 'range_end' => (int)$row['epr_bib_finish'], + 'no_bib' => (int)$row['no_bib'], + 'participants' => [], + ]; + } + $grouped[$key]['participants'][] = [ + 'par_id' => (int)$row['par_id'], + 'par_prenom' => trim($row['par_prenom'] ?? ''), + 'par_nom' => trim($row['par_nom'] ?? ''), + ]; + } + + 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). + * * @return array> */ function fxBibAnomalyDuplicateBibs($int_eve_id, $strLangue = 'fr') { @@ -4134,7 +4179,7 @@ function fxBibAnomalyDuplicateBibs($int_eve_id, $strLangue = 'fr') { } $sqlBibNumP = fxBibSqlNoBibUnsigned('p.no_bib'); - $sql = " + $sqlInRange = " SELECT b.epr_bib_id, b.epr_id, @@ -4165,33 +4210,48 @@ function fxBibAnomalyDuplicateBibs($int_eve_id, $strLangue = 'fr') { ORDER BY b.epr_id, b.epr_bib_start, $sqlBibNumP, p.par_nom, p.par_prenom "; - $rows = $objDatabase->fxGetResults($sql); - if (!is_array($rows) || empty($rows)) { + $sqlNoRange = " + SELECT + 0 AS epr_bib_id, + p.epr_id, + 0 AS epr_bib_start, + 0 AS epr_bib_finish, + 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, + $sqlBibNumP AS no_bib, + p.par_id, + p.par_prenom, + p.par_nom + 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 NOT " . fxBibSqlEpreuveHasBibRanges('p.epr_id') . " + AND " . fxBibSqlIsBibDuplicateAnomaly('p.epr_id', $sqlBibNumP) . " + ORDER BY p.epr_id, $sqlBibNumP, p.par_nom, p.par_prenom + "; + + $rows = $objDatabase->fxGetResults($sqlInRange); + if (!is_array($rows)) { + $rows = []; + } + + $rowsNoRange = $objDatabase->fxGetResults($sqlNoRange); + if (is_array($rowsNoRange) && !empty($rowsNoRange)) { + $rows = array_merge($rows, $rowsNoRange); + } + + if (empty($rows)) { return []; } - $grouped = []; - foreach ($rows as $row) { - $key = (int)$row['epr_bib_id'] . '-' . (int)$row['no_bib']; - if (!isset($grouped[$key])) { - $grouped[$key] = [ - 'epr_id' => (int)$row['epr_id'], - 'epr_label' => fxBibAnomalyEprLabel($row, $strLangue), - 'range_id' => (int)$row['epr_bib_id'], - 'range_start' => (int)$row['epr_bib_start'], - 'range_end' => (int)$row['epr_bib_finish'], - 'no_bib' => (int)$row['no_bib'], - 'participants' => [], - ]; - } - $grouped[$key]['participants'][] = [ - 'par_id' => (int)$row['par_id'], - 'par_prenom' => trim($row['par_prenom'] ?? ''), - 'par_nom' => trim($row['par_nom'] ?? ''), - ]; - } - - return array_values($grouped); + return fxBibAnomalyGroupDuplicateRows($rows, $strLangue); } /** @@ -4380,7 +4440,7 @@ function fxBibAnomalyAutoMissBibs($int_eve_id, $strLangue = 'fr') { /** * MSIN-4379 — Dossards assignés hors de toute séquence configurée pour l'épreuve. * Couvre la saisie manuelle (fiche coureur) ou tout assignement ne passant pas par les plages. - * On ne signale que les épreuves qui ont au moins une séquence (sinon rien à comparer). + * Inclut les épreuves sans aucune séquence (dossards uniques seulement — les doublons vont dans dupes). * * @return array> */ @@ -4412,20 +4472,22 @@ function fxBibAnomalyOutOfRangeBibs($int_eve_id, $strLangue = 'fr') { WHERE e.eve_id = $int_eve_id AND p.is_cancelled = 0 AND $sqlBibNumP > 0 - AND EXISTS ( - SELECT 1 - FROM inscriptions_epreuves_bib b0 - WHERE b0.epr_id = p.epr_id - AND b0.epr_bib_start IS NOT NULL - AND b0.epr_bib_finish IS NOT NULL - ) - AND NOT EXISTS ( - SELECT 1 - FROM inscriptions_epreuves_bib b - WHERE b.epr_id = p.epr_id - AND b.epr_bib_start IS NOT NULL - AND b.epr_bib_finish IS NOT NULL - AND $sqlBibNumP BETWEEN b.epr_bib_start AND b.epr_bib_finish + AND ( + ( + " . fxBibSqlEpreuveHasBibRanges('p.epr_id') . " + AND NOT EXISTS ( + SELECT 1 + FROM inscriptions_epreuves_bib b + WHERE b.epr_id = p.epr_id + AND b.epr_bib_start IS NOT NULL + AND b.epr_bib_finish IS NOT NULL + AND $sqlBibNumP BETWEEN b.epr_bib_start AND b.epr_bib_finish + ) + ) + OR ( + NOT " . fxBibSqlEpreuveHasBibRanges('p.epr_id') . " + AND NOT " . fxBibSqlIsBibDuplicateAnomaly('p.epr_id', $sqlBibNumP) . " + ) ) ORDER BY p.epr_id, $sqlBibNumP, p.par_nom, p.par_prenom "; @@ -4512,12 +4574,19 @@ function renderBibAnomaliesPanel($int_eve_id, $strLangue = 'fr', $tabAnomalies =
  • - — - — 0 || (int)$item['range_end'] > 0) { ?> + + — + + + — + + diff --git a/sql/MSIN-4379-bib-v4-anomalies-out-of-range.sql b/sql/MSIN-4379-bib-v4-anomalies-out-of-range.sql index 8f35db3..3bda76e 100644 --- a/sql/MSIN-4379-bib-v4-anomalies-out-of-range.sql +++ b/sql/MSIN-4379-bib-v4-anomalies-out-of-range.sql @@ -9,3 +9,11 @@ FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomali 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_out_of_range_title', 'en', 'Bibs outside sequences', 'Number assigned to an active runner but not falling within any sequence configured for their race (manual entry on profile, import, etc.).', 'compte.php', '', 0, 1, '', '', '', NOW() FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_out_of_range_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_anomalies_no_range', 'fr', 'Aucune séquence configurée', 'L''épreuve n''a pas de plage de dossards ; les doublons y sont listés dans la section « Dossards en double ».', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_no_range' 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_no_range', 'en', 'No sequence configured', 'This race has no bib range; duplicates are listed under « Duplicate bib numbers ».', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_no_range' AND info_langue = 'en' AND info_prg = 'compte.php');