diff --git a/ajax_bib_range.php b/ajax_bib_range.php
index 471563b..d4beeeb 100644
--- a/ajax_bib_range.php
+++ b/ajax_bib_range.php
@@ -34,6 +34,7 @@ $arrBibAjaxActions = [
'batch_apply_chunk',
'reset_preview',
'reset_apply',
+ 'reset_all_apply',
'save_team_mode',
'toggle_lock',
'save_auto_config',
@@ -905,6 +906,72 @@ if ($action == 'reset_apply') {
exit;
}
// =====================
+// RESET ALL ÉPREUVE
+// =====================
+if ($action == 'reset_all_apply') {
+
+ $epr_id = intval($_POST['epr_id'] ?? 0);
+
+ if ($epr_id <= 0) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => fxBibMsg('bib_v4_ajax_params_invalid'),
+ ]);
+ exit;
+ }
+
+ $lockCheck = fxBibAssertEpreuveNoLockedRanges($epr_id);
+ if (!$lockCheck['success']) {
+ echo json_encode([
+ 'success' => false,
+ 'message' => $lockCheck['message'] ?? fxBibMsg('bib_v4_js_reset_all_locked'),
+ ]);
+ exit;
+ }
+
+ $participants = fxGetParticipantsForEpreuveResetAll($epr_id);
+
+ $count = 0;
+
+ foreach ($participants as $p) {
+
+ $par_id = intval($p['par_id']);
+
+ if ($par_id <= 0) {
+ continue;
+ }
+
+ $sql = "
+ UPDATE resultats_participants
+ SET no_bib = NULL
+ WHERE par_id = $par_id
+ AND is_cancelled = 0
+ ";
+
+ $objDatabase->fxQuery($sql);
+
+ $count++;
+ }
+
+ $html = renderBibView(
+ $epr_id,
+ 0,
+ $strLangue,
+ 'reset',
+ $participants,
+ fxBibTexte('bib_v4_ajax_title_reset_all_done'),
+ fxBibMsg('bib_v4_ajax_reset_done_count', $count),
+ 'success'
+ );
+
+ echo json_encode([
+ 'success' => true,
+ 'html' => $html,
+ ]);
+
+ exit;
+}
+// =====================
// SAVE TEAM MODE
// =====================
if ($action == 'save_team_mode') {
diff --git a/css/style.css b/css/style.css
index 8cc427c..b1aaac5 100644
--- a/css/style.css
+++ b/css/style.css
@@ -1288,6 +1288,22 @@ ul.ms1-menu-compte li a:hover, ul.ms1-menu-compte li a:active {
margin-top:0;
}
+/* MSIN-4436 — Réinitialisation totale, sous l'en-tête identité. */
+.epr-identity-reset-all{
+ flex-shrink:0;
+ padding:6px 8px 8px;
+ background:#fff;
+ border-top:1px solid #e8edf5;
+}
+
+.epr-identity-reset-all .btn-reset-all-epr{
+ width:100%;
+ white-space:normal;
+ line-height:1.25;
+ font-size:11px;
+ padding:5px 8px;
+}
+
.epr-col-right{
display:flex;
flex-direction:column;
diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js
index 74bde24..3e068df 100644
--- a/js/v2/bib-assignments.js
+++ b/js/v2/bib-assignments.js
@@ -1544,6 +1544,33 @@
});
}
+ function bibSyncResetAllEprButton(row) {
+ if (!row) {
+ return;
+ }
+
+ let wrap = row.querySelector('.epr-identity-reset-all');
+ if (!wrap) {
+ return;
+ }
+
+ let avecBib = parseInt(bibQuery(row, '.bib-avec')?.textContent || '0', 10) || 0;
+ if (avecBib <= 0) {
+ wrap.classList.add('bib-ui-hidden');
+ return;
+ }
+
+ wrap.classList.remove('bib-ui-hidden');
+
+ let btn = wrap.querySelector('.btn-reset-all-epr');
+ if (!btn) {
+ return;
+ }
+
+ let hasLock = row.querySelector('.btn-bib-lock--closed') !== null;
+ btn.disabled = hasLock;
+ }
+
function syncBibUiState(row) {
if (!row) return;
@@ -1599,6 +1626,7 @@
bibSetVisible('.epr-batch-order-wrap', showBatchReady && !modes.reset, row);
bibSetVisible('.epr-action-sim-group', showBatchReady && !modes.reset, row);
bibSetVisible('.epr-action-go-group', showBatchReady, row);
+ bibSyncResetAllEprButton(row);
}
function updateBatchAnalysis(row) {
@@ -1903,12 +1931,65 @@
if (data.html) {
setBibContainerHtml(row, data.html);
+ bibSyncResetAllEprButton(row);
}
});
return;
}
+ // =====================
+ // MSIN-4436 — Réinitialiser tous les dossards (épreuve entière)
+ // =====================
+ let btnResetAllEpr = e.target.closest('.btn-reset-all-epr');
+ if (btnResetAllEpr) {
+
+ e.preventDefault();
+
+ let row = btnResetAllEpr.closest('.epr-row');
+ if (!row) {
+ return;
+ }
+
+ if (btnResetAllEpr.disabled) {
+ alert(bibJsConfirm('jsResetAllLocked'));
+ return;
+ }
+
+ if (row.querySelector('.btn-bib-lock--closed')) {
+ alert(bibJsConfirm('jsResetAllLocked'));
+ bibSyncResetAllEprButton(row);
+ return;
+ }
+
+ if (!confirm(bibJsConfirm('jsResetAllConfirm'))) {
+ return;
+ }
+
+ bibFetch('/ajax_bib_range.php', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body:
+ 'action=reset_all_apply'
+ + '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ + bibAjaxLangSuffix()
+ })
+ .then(res => res.json())
+ .then(data => {
+
+ if (!data.success) {
+ alert(data.message || bibJs('jsErrGeneric'));
+ return;
+ }
+
+ bibAfterBatchGoSuccess(row, data, false);
+ });
+
+ return;
+ }
+
// =====================
// DELETE
// =====================
diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php
index b177f95..12e42c1 100644
--- a/php/inc_fx_promoteur.php
+++ b/php/inc_fx_promoteur.php
@@ -2742,6 +2742,35 @@ function fxBibRequirePromoteurSession() {
return !empty($_SESSION['com_id']) || !empty($_SESSION['usa_id']);
}
+/** MSIN-4436 — Au moins une séquence verrouillée sur l'épreuve. */
+function fxBibEpreuveHasLockedRanges($epr_id) {
+ global $objDatabase;
+
+ $epr_id = (int)$epr_id;
+
+ if ($epr_id <= 0) {
+ return false;
+ }
+
+ $sql = "
+ SELECT COUNT(*)
+ FROM inscriptions_epreuves_bib
+ WHERE epr_id = $epr_id
+ AND epr_bib_locked = 1
+ ";
+
+ return (int)$objDatabase->fxGetVar($sql) > 0;
+}
+
+/** MSIN-4436 — Réinitialisation totale : aucun cadenas sur l'épreuve. */
+function fxBibAssertEpreuveNoLockedRanges($epr_id) {
+ if (fxBibEpreuveHasLockedRanges($epr_id)) {
+ return ['success' => false, 'message' => fxBibMsg('bib_v4_js_reset_all_locked')];
+ }
+
+ return ['success' => true];
+}
+
/** Resout eve_id depuis la requete AJAX bib (eve_id direct ou via epr_id). */
function fxBibResolveEveIdFromRequest() {
global $objDatabase;
@@ -3114,6 +3143,8 @@ function fxBibModuleJsDataAttrs() {
'batch-progress' => 'bib_v4_js_batch_progress',
'global-no-epr' => 'bib_v4_js_global_no_epr',
'global-no-seq' => 'bib_v4_js_global_no_seq',
+ 'reset-all-confirm' => 'bib_v4_js_reset_all_confirm',
+ 'reset-all-locked' => 'bib_v4_js_reset_all_locked',
'assign-full' => 'bib_v4_ajax_assign_full',
'assign-partial' => 'bib_v4_ajax_assign_partial',
'assign-none' => 'bib_v4_ajax_assign_none',
@@ -3502,6 +3533,29 @@ function renderBibEpreuveIdentityHeader($tabEpreuve, $infoBib, $strLangue = 'fr'
return ob_get_clean();
}
+/** MSIN-4436 — Réinitialiser tous les dossards de l'épreuve (sans sélection de séquences). */
+function renderBibEpreuveResetAllButton($epr_id, $infoBib, $blnHasLockedRanges = false) {
+ if ((int)($infoBib['avec_bib'] ?? 0) <= 0) {
+ return '';
+ }
+
+ $strTipClef = $blnHasLockedRanges ? 'bib_v4_tip_reset_all_locked' : 'bib_v4_tip_reset_all';
+
+ ob_start();
+ ?>
+
+
$nbManque
];
}
+/** MSIN-4436 — Tous les participants avec dossard sur l'épreuve (y compris hors séquences). */
+function fxGetParticipantsForEpreuveResetAll($epr_id) {
+ global $objDatabase;
+
+ $epr_id = (int)$epr_id;
+
+ if ($epr_id <= 0) {
+ return [];
+ }
+
+ $sqlBibNumP = fxBibSqlNoBibUnsigned('p.no_bib');
+
+ $sql = "
+ SELECT p.*
+ FROM resultats_participants p
+ WHERE p.epr_id = $epr_id
+ AND p.is_cancelled = 0
+ AND p.no_bib IS NOT NULL
+ AND p.no_bib > 0
+ ORDER BY $sqlBibNumP
+ ";
+
+ return $objDatabase->fxGetResults($sql);
+}
+
function fxGetParticipantsForBatchReset($epr_id, $ranges) {
global $objDatabase;
diff --git a/php/inc_settings.php b/php/inc_settings.php
index 01f1608..83d8826 100644
--- a/php/inc_settings.php
+++ b/php/inc_settings.php
@@ -7,7 +7,7 @@
* Constantes *
*
**************/
-define('_VERSION_CODE', '4.72.745');
+define('_VERSION_CODE', '4.72.747');
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-4436-reset-all-epr.sql b/sql/MSIN-4436-reset-all-epr.sql
new file mode 100644
index 0000000..ac04aab
--- /dev/null
+++ b/sql/MSIN-4436-reset-all-epr.sql
@@ -0,0 +1,50 @@
+-- MSIN-4436 — Réinitialiser tous les dossards d'une épreuve
+-- Idempotent. Exécuter après déploiement PHP/JS.
+
+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_reset_all', 'fr', 'Réinitialiser tous les dossards', 'Retire tous les numéros assignés sur cette épreuve, y compris les dossards saisis manuellement. Aucune sélection de séquence requise.', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_reset_all' 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_reset_all', 'en', 'Reset all bibs', 'Removes every assigned bib on this race, including manually entered numbers. No sequence selection required.', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_reset_all' 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_tip_reset_all', 'fr', 'Retire tous les dossards de l''épreuve (assignation batch, auto et manuelle).', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_tip_reset_all' 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_tip_reset_all', 'en', 'Removes all bibs on this race (batch, auto, and manual).', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_tip_reset_all' 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_tip_reset_all_locked', 'fr', 'Déverrouillez toutes les séquences (cadenas) avant de réinitialiser.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_tip_reset_all_locked' 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_tip_reset_all_locked', 'en', 'Unlock all sequences (padlocks) before resetting.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_tip_reset_all_locked' 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_js_reset_all_confirm', 'fr', 'Attention : tous les dossards de cette épreuve seront retirés.\n\nCela inclut les dossards assignés manuellement, en lot ou automatiquement.\n\nCette action est irréversible. Continuer ?', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_reset_all_confirm' 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_js_reset_all_confirm', 'en', 'Warning: all bibs on this race will be removed.\n\nThis includes manually assigned, batch, and automatic bibs.\n\nThis cannot be undone. Continue?', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_reset_all_confirm' 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_js_reset_all_locked', 'fr', 'Impossible : au moins une séquence est verrouillée.\n\nDéverrouillez tous les cadenas à droite avant de réinitialiser tous les dossards.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_reset_all_locked' 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_js_reset_all_locked', 'en', 'Cannot proceed: at least one sequence is locked.\n\nUnlock all padlocks on the right before resetting all bibs.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_reset_all_locked' 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_ajax_title_reset_all_done', 'fr', 'Réinitialisation totale terminée', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_title_reset_all_done' 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_ajax_title_reset_all_done', 'en', 'Full reset completed', '', 'compte.php', '', 0, 1, '', '', '', NOW()
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_title_reset_all_done' AND info_langue = 'en' AND info_prg = 'compte.php');