diff --git a/ajax_bib_range.php b/ajax_bib_range.php index addb40a..b03c991 100644 --- a/ajax_bib_range.php +++ b/ajax_bib_range.php @@ -723,13 +723,14 @@ if ($action == 'batch_global_free_analysis') { } $tabPlan = fxBibBuildFreeRangePlan($int_eve_id, $tabEprIds, $intStart, $intEnd, $strSort1, $strSort2); - $tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($tabPlan, $intStart, $intEnd); + $tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd); echo json_encode([ 'success' => true, 'info' => $tabAnalysis['info'], 'stats' => $tabAnalysis['stats'], 'feedback' => $tabAnalysis['feedback'], + 'duplicates' => $tabAnalysis['duplicates'], ]); exit; } diff --git a/css/style.css b/css/style.css index c7e9002..d2c019b 100644 --- a/css/style.css +++ b/css/style.css @@ -1472,6 +1472,10 @@ ul.ms1-menu-compte li a:hover, ul.ms1-menu-compte li a:active { .bib-global-free-range__feedback{ margin-top:6px; } +.bib-global-free-range__qty, +.bib-global-free-range__dupes{ + font-size:12.5px; +} .bib-global-free-range__feedback--juste{ color:#155724; font-weight:600; @@ -1484,6 +1488,11 @@ ul.ms1-menu-compte li a:hover, ul.ms1-menu-compte li a:active { color:#004085; font-weight:600; } +.bib-global-free-range__dupes{ + color:#a94442; + font-weight:600; + margin-top:2px; +} .bib-global-epr--inactive{ opacity:0.55; background:#f1f3f5; diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js index 4fee8c0..b8354e8 100644 --- a/js/v2/bib-assignments.js +++ b/js/v2/bib-assignments.js @@ -1238,7 +1238,9 @@ start: wrap.querySelector('.bib-global-free-start'), qty: wrap.querySelector('.bib-global-free-qty'), end: wrap.querySelector('.bib-global-free-end'), - feedback: wrap.querySelector('.bib-global-free-range__feedback') + feedback: wrap.querySelector('.bib-global-free-range__feedback'), + qtyEl: wrap.querySelector('.bib-global-free-range__qty'), + dupesEl: wrap.querySelector('.bib-global-free-range__dupes') }; } @@ -1285,32 +1287,56 @@ || (('Quantité manquante : %d').replace('%d', String(Math.abs(diff)))); } + function bibSetGlobalFreeDupesWarn(section, blnShow, strMessage) { + var fields = bibGetGlobalBatchFreeRangeFields(section); + if (!fields || !fields.dupesEl) { + return; + } + + if (!blnShow) { + fields.dupesEl.textContent = ''; + fields.dupesEl.classList.add('bib-ui-hidden'); + return; + } + + fields.dupesEl.textContent = strMessage + || bibJs('jsGlobalFreeDupesWarn') + || 'Attention : vous êtes en train de créer des inscriptions avec des dossards en double.'; + fields.dupesEl.classList.remove('bib-ui-hidden'); + } + function bibUpdateGlobalFreeQtyFeedback(section) { var fields = bibGetGlobalBatchFreeRangeFields(section); - if (!fields || !fields.feedback) { + if (!fields) { return; } var bounds = bibGetGlobalBatchFreeBounds(section); var pending = bibCollectGlobalBatchFreePending(); var msg = bibFormatGlobalFreeQtyFeedback(bounds.qty, pending); - fields.feedback.textContent = msg; - fields.feedback.classList.remove( - 'bib-global-free-range__feedback--juste', - 'bib-global-free-range__feedback--manquante', - 'bib-global-free-range__feedback--supplementaire' - ); + var qtyEl = fields.qtyEl || fields.feedback; - if (!msg) { - return; + if (qtyEl) { + qtyEl.textContent = msg; + qtyEl.classList.remove( + 'bib-global-free-range__feedback--juste', + 'bib-global-free-range__feedback--manquante', + 'bib-global-free-range__feedback--supplementaire' + ); + + if (msg) { + if (bounds.qty === pending) { + qtyEl.classList.add('bib-global-free-range__feedback--juste'); + } else if (bounds.qty < pending) { + qtyEl.classList.add('bib-global-free-range__feedback--manquante'); + } else { + qtyEl.classList.add('bib-global-free-range__feedback--supplementaire'); + } + } } - if (bounds.qty === pending) { - fields.feedback.classList.add('bib-global-free-range__feedback--juste'); - } else if (bounds.qty < pending) { - fields.feedback.classList.add('bib-global-free-range__feedback--manquante'); - } else { - fields.feedback.classList.add('bib-global-free-range__feedback--supplementaire'); + if (!msg) { + bibSetGlobalFreeDupesWarn(section, false); } } @@ -1492,6 +1518,7 @@ bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone'), 'error' ); + bibSetGlobalFreeDupesWarn(section, false); return Promise.resolve(null); } @@ -1501,6 +1528,7 @@ bibJs('jsGlobalFreePlanInvalid') || bibJs('jsFieldsRequired'), 'error' ); + bibSetGlobalFreeDupesWarn(section, false); return Promise.resolve(null); } @@ -1530,11 +1558,21 @@ if (data.feedback && data.feedback.status === 'manquante') { state = 'warning'; } + if (data.duplicates && data.duplicates.has) { + state = 'warning'; + } bibSetGlobalBatchInfo(infoEl, data.info || '', state); + bibUpdateGlobalFreeQtyFeedback(section); + bibSetGlobalFreeDupesWarn( + section, + !!(data.duplicates && data.duplicates.has), + (data.duplicates && data.duplicates.message) || '' + ); return data; }) .catch(function () { bibSetGlobalBatchInfo(infoEl, bibJs('jsErrGeneric'), 'error'); + bibSetGlobalFreeDupesWarn(section, false); return null; }); } diff --git a/php/inc_fx_bib_global_free.php b/php/inc_fx_bib_global_free.php index 591cbbe..4e16998 100644 --- a/php/inc_fx_bib_global_free.php +++ b/php/inc_fx_bib_global_free.php @@ -60,6 +60,73 @@ function fxBibFreeQtyFeedbackMessage(array $tabFeedback) { return ''; } +/** + * MSIN-4445 — Nombre de dossards de la plage déjà utilisés sur une autre épreuve. + * Ces numéros seraient réassignés (doublon inter-épreuve) car le batch ne les saute pas. + */ +function fxBibCountBibsUsedOutsideEpr($int_eve_id, $intStart, $intEnd, $intEprId) { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + $intStart = (int)$intStart; + $intEnd = (int)$intEnd; + $intEprId = (int)$intEprId; + + if ($int_eve_id <= 0 || $intEprId <= 0 || $intStart <= 0 || $intEnd < $intStart) { + return 0; + } + + $sqlBib = fxBibSqlNoBibUnsigned('no_bib'); + + return (int)$objDatabase->fxGetVar(" + SELECT COUNT(DISTINCT $sqlBib) + FROM resultats_participants + WHERE eve_id = $int_eve_id + AND epr_id <> $intEprId + AND is_cancelled = 0 + AND no_bib IS NOT NULL + AND TRIM(no_bib) <> '' + AND $sqlBib BETWEEN $intStart AND $intEnd + "); +} + +/** + * MSIN-4445 — Risque de doublons pour le plan libre (plages virtuelles vs dossards déjà pris ailleurs). + * + * @return array{has:bool, count:int, message:string} + */ +function fxBibFreeDuplicateRisk($int_eve_id, $tabPlan) { + $int_eve_id = (int)$int_eve_id; + $intCount = 0; + + if (!is_array($tabPlan)) { + return ['has' => false, 'count' => 0, 'message' => '']; + } + + foreach ($tabPlan as $item) { + if (!empty($item['skip'])) { + continue; + } + + $intCount += fxBibCountBibsUsedOutsideEpr( + $int_eve_id, + (int)($item['start'] ?? 0), + (int)($item['end'] ?? 0), + (int)($item['epr_id'] ?? 0) + ); + } + + if ($intCount <= 0) { + return ['has' => false, 'count' => 0, 'message' => '']; + } + + return [ + 'has' => true, + 'count' => $intCount, + 'message' => fxBibMsg('bib_v4_global_batch_free_dupes_warn'), + ]; +} + /** * Plan de plages virtuelles bornées par Début/Fin (sans écriture BD). * Enchaîne les épreuves comme le bloc généré, mais s'arrête à $intEnd. @@ -181,7 +248,8 @@ function fxBibValidateFreeRangePlan($int_eve_id, $tabPlan, $intStart, $intEnd, $ } /** Stats agrégées + feedback quantité pour l'analyse libre. */ -function fxBibBuildGlobalBatchFreeAnalysis($tabPlan, $intStart, $intEnd) { +function fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd) { + $int_eve_id = (int)$int_eve_id; $intStart = (int)$intStart; $intEnd = (int)$intEnd; $intQty = ($intStart > 0 && $intEnd >= $intStart) ? ($intEnd - $intStart + 1) : 0; @@ -194,11 +262,13 @@ function fxBibBuildGlobalBatchFreeAnalysis($tabPlan, $intStart, $intEnd) { } $tabFeedback = fxBibFreeQtyFeedback($intQty, $intPendingAll); + $tabDupes = fxBibFreeDuplicateRisk($int_eve_id, $tabPlan); return [ - 'stats' => $tabStats, - 'feedback' => $tabFeedback, - 'info' => fxBibMsg( + 'stats' => $tabStats, + 'feedback' => $tabFeedback, + 'duplicates' => $tabDupes, + 'info' => fxBibMsg( 'bib_v4_ajax_global_free_analysis', $tabStats['nb_epreuves'], $intStart, @@ -271,7 +341,7 @@ function fxBibSimulateGlobalBatchFree($int_eve_id, $tabPlan, $intStart, $intEnd, . ''; } - $tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($tabPlan, $intStart, $intEnd); + $tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd); $info = fxBibMsg( 'bib_v4_ajax_global_free_sim_summary', $tabAnalysis['stats']['nb_epreuves'], @@ -316,12 +386,13 @@ function fxBibPrepareFreeRangePlans($int_eve_id, $tabPlan, $intStart, $intEnd, $ ]; } - $tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($tabPlan, $intStart, $intEnd); + $tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd); return [ 'success' => true, 'plans' => $tabPlans, 'stats' => $tabAnalysis['stats'], 'feedback' => $tabAnalysis['feedback'], + 'duplicates' => $tabAnalysis['duplicates'], ]; } diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index a55fd7d..36454ac 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -3450,6 +3450,7 @@ function fxBibStaticFallback($clef) { 'bib_v4_global_batch_free_qty_juste' => ['fr' => 'Quantité juste', 'en' => 'Exact quantity'], 'bib_v4_global_batch_free_qty_manquante' => ['fr' => 'Quantité manquante : %d', 'en' => 'Missing quantity: %d'], 'bib_v4_global_batch_free_qty_supplementaire' => ['fr' => 'Quantité supplémentaire : %d', 'en' => 'Extra quantity: %d'], + 'bib_v4_global_batch_free_dupes_warn' => ['fr' => 'Attention : vous êtes en train de créer des inscriptions avec des dossards en double.', 'en' => 'Warning: you are about to create registrations with duplicate bib numbers.'], 'bib_v4_global_batch_free_plan_invalid' => ['fr' => 'Sélection invalide : Début, Fin (ou Qté) et au moins une épreuve avec inscriptions à assigner.', 'en' => 'Invalid selection: Start, End (or Qty) and at least one race with registrations to assign.'], 'bib_v4_ajax_global_free_analysis' => ['fr' => '%d épreuve(s) · plage %d → %d · %d à assigner · %s', 'en' => '%d race(s) · range %d → %d · %d to assign · %s'], 'bib_v4_ajax_global_free_sim_summary' => ['fr' => '%d épreuve(s) · %d dossards · %d à assigner · %s', 'en' => '%d race(s) · %d bibs · %d to assign · %s'], @@ -3714,6 +3715,7 @@ function fxBibModuleJsDataAttrs() { 'global-free-qty-juste' => 'bib_v4_global_batch_free_qty_juste', 'global-free-qty-manquante' => 'bib_v4_global_batch_free_qty_manquante', 'global-free-qty-supplementaire' => 'bib_v4_global_batch_free_qty_supplementaire', + 'global-free-dupes-warn' => 'bib_v4_global_batch_free_dupes_warn', 'global-free-plan-invalid' => 'bib_v4_global_batch_free_plan_invalid', 'reset-all-confirm' => 'bib_v4_js_reset_all_confirm', 'reset-all-locked' => 'bib_v4_js_reset_all_locked', @@ -5017,7 +5019,10 @@ function renderBibGlobalBatchPanelFree($int_eve_id, $tabEpreuves, $strLangue) { inputmode="numeric" disabled> -
+