Implement batch processing enhancements for bib management system

This commit introduces a new action, `batch_apply_chunk`, to facilitate chunked processing of batch assignments in the bib management system. It enhances the AJAX handling for batch operations, allowing for better progress tracking and user feedback during the assignment process. Additionally, new JavaScript functions are added to manage batch progress display, and CSS styles are updated to support the new loading indicators. The version code is incremented to reflect these changes, aiming to improve user experience and efficiency in batch processing.
This commit is contained in:
2026-07-07 15:16:23 -04:00
parent d90df7c46f
commit b3e3c72be5
7 changed files with 580 additions and 203 deletions

View File

@ -31,6 +31,7 @@ $arrBibAjaxActions = [
'refresh_ranges_data',
'batch_go',
'batch_apply',
'batch_apply_chunk',
'reset_preview',
'reset_apply',
'save_team_mode',
@ -515,46 +516,7 @@ if ($action == 'batch_apply') {
$exec = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'execute');
$tabRangesInfos = fxInfosBibRange($epr_id);
$ids = array_map('intval', $ranges);
$tabRangesInfos = array_filter($tabRangesInfos, function($r) use ($ids) {
return in_array((int)$r['epr_bib_id'], $ids);
});
$summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id);
$assigned = count($exec);
$total = count($participants);
$remaining = max(0, $total - $assigned);
$status = 'success';
if ($assigned === 0) {
$status = 'error';
} elseif ($assigned < $total) {
$status = 'partial';
}
if ($assigned === 0) {
$info = fxBibMsg('bib_v4_ajax_assign_none');
} elseif ($assigned === $total) {
$info = fxBibMsg('bib_v4_ajax_assign_full', $assigned);
} else {
$info = fxBibMsg('bib_v4_ajax_assign_partial', $assigned, $remaining);
}
$html = renderBibView(
$epr_id,
0,
$strLangue,
'execute',
$exec,
fxBibTexte('bib_v4_ajax_title_assign'),
$info,$status
);
$html = fxBibBuildBatchApplyHtml($epr_id, $exec, count($participants), $strLangue);
echo json_encode([
'success' => true,
@ -563,6 +525,98 @@ if ($action == 'batch_apply') {
exit;
}
// MSIN-4433 — GO batch par paquets : progression réelle + relecture BD entre paquets.
if ($action == 'batch_apply_chunk') {
$epr_id = intval($_POST['epr_id'] ?? 0);
$ranges = json_decode($_POST['ranges'] ?? '[]', true);
$ba_id = intval($_POST['ba_id'] ?? 0);
$intChunkSize = (int)($_POST['chunk_size'] ?? 50);
$intTotalInitial = (int)($_POST['total_initial'] ?? 0);
$blnBatchReset = ((int)($_POST['batch_reset'] ?? 0) === 1);
if ($intChunkSize < 1) {
$intChunkSize = 50;
}
if ($intChunkSize > 100) {
$intChunkSize = 100;
}
$lockCheck = fxBibAssertRangesUnlocked($epr_id, is_array($ranges) ? $ranges : []);
if (!$lockCheck['success']) {
echo json_encode([
'success' => false,
'message' => $lockCheck['message'] ?? fxBibMsg('bib_v4_ajax_range_locked')
]);
exit;
}
$strSessionKey = fxBibBatchExecSessionKey($epr_id);
if ($blnBatchReset) {
$_SESSION[$strSessionKey] = [];
} elseif (!isset($_SESSION[$strSessionKey]) || !is_array($_SESSION[$strSessionKey])) {
$_SESSION[$strSessionKey] = [];
}
$participants = fxGetParticipantsForBatchAssign($epr_id, $ba_id);
if ($intTotalInitial <= 0) {
$intTotalInitial = count($participants) + count($_SESSION[$strSessionKey]);
}
if (empty($participants)) {
$execAll = $_SESSION[$strSessionKey];
unset($_SESSION[$strSessionKey]);
$html = fxBibBuildBatchApplyHtml($epr_id, $execAll, $intTotalInitial, $strLangue);
echo json_encode([
'success' => true,
'complete' => true,
'total_initial' => $intTotalInitial,
'processed' => $intTotalInitial,
'assigned_total' => count($execAll),
'assigned_chunk' => 0,
'html' => $html
]);
exit;
}
$tabChunk = array_slice($participants, 0, $intChunkSize);
$tabRanges = fxGetRangesForBatchAssign($epr_id, $ranges);
$execChunk = fxProcessBatchAssignments($epr_id, $tabChunk, $tabRanges, 'execute');
$_SESSION[$strSessionKey] = array_merge($_SESSION[$strSessionKey], $execChunk);
$intRemaining = count(fxGetParticipantsForBatchAssign($epr_id, $ba_id));
$intProcessed = max(0, $intTotalInitial - $intRemaining);
$blnComplete = ($intRemaining === 0);
// MSIN-4433 — Dossards épuisés : clôturer si aucune progression sur ce paquet.
if (!$blnComplete && empty($execChunk)) {
$blnComplete = true;
}
$arrResponse = [
'success' => true,
'complete' => $blnComplete,
'total_initial' => $intTotalInitial,
'processed' => $intProcessed,
'assigned_total' => count($_SESSION[$strSessionKey]),
'assigned_chunk' => count($execChunk),
];
if ($blnComplete) {
$execAll = $_SESSION[$strSessionKey];
unset($_SESSION[$strSessionKey]);
$arrResponse['html'] = fxBibBuildBatchApplyHtml($epr_id, $execAll, $intTotalInitial, $strLangue);
}
echo json_encode($arrResponse);
exit;
}
// =====================
// RESET PREVIEW
// =====================

View File

@ -68,3 +68,33 @@
font-size: 1.1rem;
font-weight: 600;
}
.ms1-loader__progress {
width: 100%;
min-width: 220px;
margin-top: 0.25rem;
}
.ms1-loader__progress-track {
width: 100%;
height: 8px;
background: #e8e8e8;
border-radius: 4px;
overflow: hidden;
}
.ms1-loader__progress-fill {
height: 100%;
width: 0;
background: linear-gradient(90deg, #2e7d32, #43a047);
border-radius: 4px;
transition: width 0.25s ease;
}
.ms1-loader__progress-pct {
display: block;
margin-top: 0.35rem;
font-size: 0.85rem;
font-weight: 600;
color: #555;
}

View File

@ -2393,6 +2393,165 @@ if ($strLangue == 'fr') {
});
}
var BIB_BATCH_CHUNK_SIZE = 50;
function bibBatchProgressShow(processed, total) {
var intTotal = total > 0 ? total : 1;
var msg = bibJsFmt('jsBatchProgress', processed, intTotal);
if (window.Ms1Loader && Ms1Loader.showProgress) {
Ms1Loader.showProgress(processed, intTotal, msg);
} else {
bibLoaderShow();
}
}
function bibBatchProgressHide() {
if (window.Ms1Loader && Ms1Loader.hide) {
Ms1Loader.hide();
} else {
bibLoaderHide();
}
}
function bibAfterBatchGoSuccess(row, data, isResetMode) {
let existing = document.querySelector('.epr-row-view');
if (existing) {
existing.remove();
}
if (data.html) {
row.insertAdjacentHTML('afterend', data.html);
}
if (isResetMode) {
row.classList.remove('reset-mode');
row.dataset.mode = '';
let btnReset = row.querySelector('.btn-reset');
if (btnReset) {
btnReset.textContent = row.dataset.resetLabel || btnReset.textContent;
}
}
row.classList.remove('batch-mode');
let container = row.querySelector('.bib-container');
if (container) {
container.classList.remove('batch-mode');
}
let btnBatch = row.querySelector('.btn-assign-batch');
if (btnBatch) {
btnBatch.textContent = row.dataset.batchLabel || btnBatch.textContent;
}
row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
input.removeAttribute('readonly');
});
row.querySelectorAll('.batch-select-range').forEach(cb => {
cb.checked = false;
cb.disabled = false;
});
syncBibUiState(row);
bibFetch('/ajax_bib_range.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body:
'action=refresh_ranges'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&mode=assign'
+ bibAjaxLangSuffix()
})
.then(res => res.json())
.then(refresh => {
if (!refresh.success) return;
let container = setBibContainerHtml(row, refresh.html);
if (!container) return;
bibFetch('/ajax_bib_range.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body:
'action=batch_analysis'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ bibAjaxLangSuffix()
})
.then(res => res.json())
.then(stats => {
if (!stats.success) return;
bibSyncEprBibStats(row, stats.info);
syncBibUiState(row);
});
});
}
function bibFetchBatchApplyChunk(row, selected, ba_id, opts) {
opts = opts || {};
var body =
'action=batch_apply_chunk'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&ranges=' + encodeURIComponent(JSON.stringify(selected))
+ '&ba_id=' + encodeURIComponent(ba_id)
+ '&chunk_size=' + BIB_BATCH_CHUNK_SIZE
+ '&total_initial=' + (opts.totalInitial || 0)
+ '&batch_reset=' + (opts.batchReset ? 1 : 0)
+ bibAjaxLangSuffix();
return fetch('/ajax_bib_range.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body
}).then(function (res) { return res.json(); });
}
function bibRunBatchApplyChunks(row, selected, ba_id) {
var totalInitial = bibRowPendingBibs(row);
var batchReset = true;
bibBatchProgressShow(0, totalInitial > 0 ? totalInitial : 1);
function runChunk() {
return bibFetchBatchApplyChunk(row, selected, ba_id, {
batchReset: batchReset,
totalInitial: totalInitial
}).then(function (data) {
if (!data.success) {
throw new Error(data.message || bibJs('jsErrGeneric'));
}
if (data.total_initial) {
totalInitial = data.total_initial;
}
batchReset = false;
var processed = data.processed || 0;
bibBatchProgressShow(processed, totalInitial);
if (data.complete) {
bibAfterBatchGoSuccess(row, data, false);
return;
}
return runChunk();
});
}
runChunk()
.catch(function (err) {
alert(err.message || bibJs('jsErrGeneric'));
})
.finally(function () {
bibBatchProgressHide();
});
}
function bibRowPendingBibs(row) {
if (!row) return 0;
return parseInt(row.dataset.bibAAssigner || '0', 10) || 0;
@ -3939,106 +4098,34 @@ if ($strLangue == 'fr') {
let isResetMode = row.dataset.mode === 'reset';
let ba_id = row.querySelector('.batch-order').value;
bibFetch('/ajax_bib_range.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body:
(isResetMode ? 'action=reset_apply' : 'action=batch_apply')
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&ranges=' + encodeURIComponent(JSON.stringify(selected))
+ '&ba_id=' + encodeURIComponent(ba_id)
+ bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
if (isResetMode) {
bibFetch('/ajax_bib_range.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body:
'action=reset_apply'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&ranges=' + encodeURIComponent(JSON.stringify(selected))
+ '&ba_id=' + encodeURIComponent(ba_id)
+ bibAjaxLangSuffix()
})
.then(res => res.json())
.then(data => {
if (!data.success) {
alert(data.message || bibJs('jsErrGeneric'));
return;
}
let existing = document.querySelector('.epr-row-view');
if (existing) {
existing.remove();
}
// afficher résultat
row.insertAdjacentHTML('afterend', data.html);
// =====================
// RESET UI BATCH
// =====================
if (isResetMode) {
row.classList.remove('reset-mode');
row.dataset.mode = '';
let btnReset = row.querySelector('.btn-reset');
if (btnReset) {
btnReset.textContent = row.dataset.resetLabel || btnReset.textContent;
if (!data.success) {
alert(data.message || bibJs('jsErrGeneric'));
return;
}
}
row.classList.remove('batch-mode');
let container = row.querySelector('.bib-container');
if (container) {
container.classList.remove('batch-mode');
}
let btnBatch = row.querySelector('.btn-assign-batch');
if (btnBatch) {
btnBatch.textContent = row.dataset.batchLabel || btnBatch.textContent;
}
row.querySelectorAll('.bib-start, .bib-qty, .bib-end').forEach(input => {
input.removeAttribute('readonly');
bibAfterBatchGoSuccess(row, data, true);
});
row.querySelectorAll('.batch-select-range').forEach(cb => {
cb.checked = false;
cb.disabled = false;
});
return;
}
syncBibUiState(row);
bibFetch('/ajax_bib_range.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body:
'action=refresh_ranges'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ '&mode=assign'
+ bibAjaxLangSuffix()
})
.then(res => res.json())
.then(refresh => {
if (!refresh.success) return;
let container = setBibContainerHtml(row, refresh.html);
if (!container) return;
bibFetch('/ajax_bib_range.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body:
'action=batch_analysis'
+ '&epr_id=' + encodeURIComponent(row.dataset.eprId)
+ bibAjaxLangSuffix()
})
.then(res => res.json())
.then(stats => {
if (!stats.success) return;
bibSyncEprBibStats(row, stats.info);
syncBibUiState(row);
});
});
});
bibRunBatchApplyChunks(row, selected, ba_id);
return;
}

View File

@ -42,6 +42,7 @@
if (!$preloader.length) {
return;
}
clearProgress();
if (message != null && message !== '') {
var $text = $('#preloader_text');
if ($text.length) {
@ -51,7 +52,54 @@
$preloader.show();
}
function clearProgress() {
$('#spinner .ms1-loader__progress').remove();
}
/**
* MSIN-4433 — Overlay avec coureur + barre de progression (% réel).
* @param {number} current
* @param {number} total
* @param {string} [message]
*/
function showProgress(current, total, message) {
var $preloader = $('#preloader');
if (!$preloader.length) {
return;
}
var intCurrent = Math.max(0, parseInt(current, 10) || 0);
var intTotal = Math.max(1, parseInt(total, 10) || 1);
var intPct = Math.min(100, Math.round((intCurrent / intTotal) * 100));
var $spinner = $('#spinner');
var $bar = $spinner.find('.ms1-loader__progress');
if (!$bar.length) {
$bar = $('<div class="ms1-loader__progress" role="progressbar" aria-valuemin="0" aria-valuemax="100">'
+ '<div class="ms1-loader__progress-track">'
+ '<div class="ms1-loader__progress-fill"></div>'
+ '</div>'
+ '<span class="ms1-loader__progress-pct"></span>'
+ '</div>');
$('#preloader_text').after($bar);
}
$bar.attr('aria-valuenow', intPct);
$bar.find('.ms1-loader__progress-fill').css('width', intPct + '%');
$bar.find('.ms1-loader__progress-pct').text(intPct + '%');
if (message != null && message !== '') {
var $text = $('#preloader_text');
if ($text.length) {
$text.html(message);
}
}
$preloader.show();
}
function hide() {
clearProgress();
$('#preloader').fadeOut('slow');
}
@ -59,6 +107,7 @@
init: init,
inlineHtml: inlineHtml,
show: show,
showProgress: showProgress,
hide: hide
};
}(window, jQuery));

View File

@ -3106,6 +3106,7 @@ function fxBibModuleJsDataAttrs() {
'range-invalid' => 'bib_v4_js_range_invalid',
'range-invalid-end' => 'bib_v4_js_range_invalid_end',
'range-invalid-qty' => 'bib_v4_js_range_invalid_qty',
'batch-progress' => 'bib_v4_js_batch_progress',
];
$html = '';
foreach ($map as $attr => $clef) {
@ -5762,47 +5763,20 @@ function fxLoadUsedBibsForBatch($epr_id) {
* @param int $teamMode epr_bib_team_mode (1 = individuel, 3 = 1 équipe = 1 dossard)
*/
function fxFindNextBatchBib($epr_id, $tabRanges, $mode, &$usedBib, $teamMode = 1) {
global $objDatabase;
$epr_id = intval($epr_id);
$teamMode = (int)$teamMode;
$sqlBibNum = fxBibSqlNoBibUnsigned('no_bib');
foreach ($tabRanges as $range) {
$start = (int)$range['start'];
$end = (int)$range['end'];
for ($i = $start; $i <= $end; $i++) {
if ($mode === 'simulation') {
// Simulation : cache mémoire (même logique solo/équipe via $teamBibMap en amont)
if (!isset($usedBib[$i])) {
if (!isset($usedBib[$i])) {
// MSIN-4433 — simulation : réserve en mémoire ; execute : réservation après UPDATE réussi.
if ($mode === 'simulation') {
$usedBib[$i] = true;
return $i;
}
} elseif ($teamMode === 3) {
// GO — mode « 1 équipe = 1 dossard »
// Le numéro est libre s'il n'est pas déjà pris par une autre équipe.
$sqlCheck = "
SELECT COUNT(DISTINCT pec_id)
FROM resultats_participants
WHERE epr_id = $epr_id
AND $sqlBibNum = $i
AND pec_id > 0
";
if ((int)$objDatabase->fxGetVar($sqlCheck) === 0) {
return $i;
}
} else {
// GO — solo ou dossards individuels : un numéro ne peut servir qu'à une personne
$sqlCheck = "
SELECT COUNT(*)
FROM resultats_participants
WHERE epr_id = $epr_id
AND $sqlBibNum = $i
";
if ((int)$objDatabase->fxGetVar($sqlCheck) === 0) {
return $i;
}
return $i;
}
}
}
@ -5810,6 +5784,84 @@ function fxFindNextBatchBib($epr_id, $tabRanges, $mode, &$usedBib, $teamMode = 1
return null;
}
/**
* MSIN-4433 — Vérifie en BD qu'un numéro est encore libre (assignation manuelle entre-temps).
*/
function fxBibIsNumberFreeInDb($epr_id, $intBib, $teamMode = 1) {
global $objDatabase;
$epr_id = (int)$epr_id;
$intBib = (int)$intBib;
$teamMode = (int)$teamMode;
$sqlBibNum = fxBibSqlNoBibUnsigned('no_bib');
if ($epr_id <= 0 || $intBib <= 0) {
return false;
}
if ($teamMode === 3) {
$sqlCheck = "
SELECT COUNT(DISTINCT pec_id)
FROM resultats_participants
WHERE epr_id = $epr_id
AND $sqlBibNum = $intBib
AND pec_id > 0
";
return (int)$objDatabase->fxGetVar($sqlCheck) === 0;
}
$sqlCheck = "
SELECT COUNT(*)
FROM resultats_participants
WHERE epr_id = $epr_id
AND $sqlBibNum = $intBib
";
return (int)$objDatabase->fxGetVar($sqlCheck) === 0;
}
/**
* MSIN-4433 — Prochain dossard libre pour un participant (1 UPDATE si execute).
* Relecture BD avant écriture : cohérence si changement manuel pendant le batch.
*
* @return int|null Numéro attribué, ou null si épuisé / participant déjà servi.
*/
function fxTryAssignNextBibForParticipant($epr_id, $par_id, $tabRanges, $mode, &$usedBib, $teamMode = 1) {
$epr_id = (int)$epr_id;
$par_id = (int)$par_id;
$teamMode = (int)$teamMode;
$intMaxAttempts = 50000;
for ($intAttempt = 0; $intAttempt < $intMaxAttempts; $intAttempt++) {
$candidate = fxFindNextBatchBib($epr_id, $tabRanges, $mode, $usedBib, $teamMode);
if ($candidate === null) {
return null;
}
if ($mode === 'execute') {
if (!fxBibIsNumberFreeInDb($epr_id, $candidate, $teamMode)) {
$usedBib[$candidate] = true;
continue;
}
$intAffected = fxApplyBatchParticipantBib($epr_id, $par_id, $candidate);
if ($intAffected <= 0) {
return null;
}
$usedBib[$candidate] = true;
return (int)$candidate;
}
return (int)$candidate;
}
return null;
}
/**
* Écrit le no_bib d'un participant (batch GO).
* Note : en mode « 1 équipe = 1 dossard », plusieurs lignes peuvent légitimement
@ -5828,10 +5880,13 @@ function fxApplyBatchParticipantBib($epr_id, $par_id, $foundBib) {
";
$objDatabase->fxQuery($sqlUpdate);
$intAffected = (int)$objDatabase->affected_rows;
if (function_exists('fxEvtErreurResoudre')) {
if ($intAffected > 0 && function_exists('fxEvtErreurResoudre')) {
fxEvtErreurResoudre('bib_auto_miss', (int)$epr_id, (int)$par_id);
}
return $intAffected;
}
function fxApplyBatchTeamNumber($epr_id, $teamKey, $teamNumber, &$updatedTeams) {
@ -5868,6 +5923,35 @@ function fxGetBatchTeamKey($p) {
return (int)($p['pec_id_original'] ?? $p['pec_id'] ?? 0);
}
/**
* MSIN-4433 — Mode équipe 3 : dossard déjà posé sur un coéquipier (batch par paquets).
*/
function fxGetExistingTeamBibInDb($epr_id, $teamKey) {
global $objDatabase;
$epr_id = (int)$epr_id;
$teamKey = (int)$teamKey;
$sqlBibNum = fxBibSqlNoBibUnsigned('no_bib');
if ($epr_id <= 0 || $teamKey <= 0) {
return null;
}
$sql = "
SELECT $sqlBibNum
FROM resultats_participants
WHERE epr_id = $epr_id
AND pec_id = $teamKey
AND is_cancelled = 0
AND $sqlBibNum > 0
LIMIT 1
";
$bib = (int)$objDatabase->fxGetVar($sql);
return $bib > 0 ? $bib : null;
}
function fxCountBatchAssignUnits($epr_id, $participants = null) {
global $objDatabase;
@ -5951,19 +6035,23 @@ function fxCountBatchAssignUnits($epr_id, $participants = null) {
function fxProcessBatchAssignmentsSolo($epr_id, $participants, $tabRanges, $mode = 'simulation') {
$assignments = [];
$usedBib = ($mode === 'simulation') ? fxLoadUsedBibsForBatch($epr_id) : [];
// MSIN-4433 — état réel rechargé à chaque paquet (execute) ou simulation.
$usedBib = fxLoadUsedBibsForBatch($epr_id);
foreach ($participants as $p) {
$foundBib = fxFindNextBatchBib($epr_id, $tabRanges, $mode, $usedBib);
$foundBib = fxTryAssignNextBibForParticipant(
$epr_id,
(int)$p['par_id'],
$tabRanges,
$mode,
$usedBib,
1
);
if ($foundBib === null) {
break;
}
if ($mode === 'execute') {
fxApplyBatchParticipantBib($epr_id, (int)$p['par_id'], $foundBib);
}
$assignments[] = fxBuildBatchAssignmentRow($p, $foundBib, 0);
}
@ -5971,16 +6059,12 @@ function fxProcessBatchAssignmentsSolo($epr_id, $participants, $tabRanges, $mode
}
function fxProcessBatchAssignmentsEquipe($epr_id, $participants, $tabRanges, $mode, $teamMode) {
global $objDatabase;
$assignments = [];
$teamMap = [];
$updatedTeams = [];
// Mode 3 : mémorise le dossard déjà choisi pour chaque équipe (pec_id)
// → les coéquipiers réutilisent ce numéro sans repasser par fxFindNextBatchBib
$teamBibMap = [];
$nextTeamNumber = 1;
$usedBib = ($mode === 'simulation') ? fxLoadUsedBibsForBatch($epr_id) : [];
$usedBib = fxLoadUsedBibsForBatch($epr_id);
foreach ($participants as $p) {
$par_id = (int)$p['par_id'];
@ -5988,37 +6072,60 @@ function fxProcessBatchAssignmentsEquipe($epr_id, $participants, $tabRanges, $mo
$teamNumber = 0;
$foundBib = null;
if ($teamMode === 3 && $teamKey > 0 && isset($teamBibMap[$teamKey])) {
// Coéquipier suivant : même dossard que le premier membre déjà traité
$foundBib = $teamBibMap[$teamKey];
if ($teamMode === 3 && $teamKey > 0) {
if (isset($teamBibMap[$teamKey])) {
$foundBib = $teamBibMap[$teamKey];
} else {
$foundBib = fxGetExistingTeamBibInDb($epr_id, $teamKey);
if ($foundBib !== null) {
$teamBibMap[$teamKey] = $foundBib;
}
}
}
if ($teamMode === 3 && $teamKey > 0 && $foundBib !== null) {
$teamNumber = $foundBib;
} elseif ($teamMode !== 3 && $teamKey > 0) {
// Mode dossards individuels : no_equipe séquentiel (1, 2, 3…) partagé par l'équipe
if (!isset($teamMap[$teamKey])) {
$teamMap[$teamKey] = $nextTeamNumber;
$nextTeamNumber++;
if ($mode === 'execute') {
$intAffected = fxApplyBatchParticipantBib($epr_id, $par_id, $foundBib);
if ($intAffected <= 0) {
continue;
}
fxApplyBatchTeamNumber($epr_id, $teamKey, $teamNumber, $updatedTeams);
}
} else {
if ($teamMode !== 3 && $teamKey > 0) {
if (!isset($teamMap[$teamKey])) {
$teamMap[$teamKey] = $nextTeamNumber;
$nextTeamNumber++;
}
$teamNumber = $teamMap[$teamKey];
}
$teamNumber = $teamMap[$teamKey];
}
$foundBib = fxTryAssignNextBibForParticipant(
$epr_id,
$par_id,
$tabRanges,
$mode,
$usedBib,
$teamMode
);
if ($foundBib === null) {
// Premier membre d'une équipe (mode 3) ou chaque participant (mode 1)
$foundBib = fxFindNextBatchBib($epr_id, $tabRanges, $mode, $usedBib, $teamMode);
}
if ($foundBib === null) {
break;
}
if ($foundBib === null) {
break;
}
if ($teamMode === 3 && $teamKey > 0) {
$teamBibMap[$teamKey] = $foundBib;
$teamNumber = $foundBib;
}
if ($teamMode === 3 && $teamKey > 0) {
$teamBibMap[$teamKey] = $foundBib;
$teamNumber = $foundBib;
}
if ($mode === 'execute') {
fxApplyBatchParticipantBib($epr_id, $par_id, $foundBib);
fxApplyBatchTeamNumber($epr_id, $teamKey, $teamNumber, $updatedTeams);
if ($mode === 'execute') {
fxApplyBatchTeamNumber($epr_id, $teamKey, $teamNumber, $updatedTeams);
}
}
$assignments[] = fxBuildBatchAssignmentRow($p, $foundBib, $teamNumber);
@ -6057,6 +6164,46 @@ function fxProcessBatchAssignments($epr_id, $participants, $tabRanges, $mode = '
return fxProcessBatchAssignmentsEquipe($epr_id, $participants, $tabRanges, $mode, $teamMode);
}
/** MSIN-4433 — Clé session pour accumuler les lignes assignées entre paquets AJAX. */
function fxBibBatchExecSessionKey($epr_id) {
return 'bib_batch_exec_' . (int)$epr_id;
}
/**
* MSIN-4433 — HTML résultat GO batch (batch_apply et dernier paquet).
*/
function fxBibBuildBatchApplyHtml($epr_id, array $exec, $intTotalInitial, $strLangue) {
$assigned = count($exec);
$total = max($assigned, (int)$intTotalInitial);
$remaining = max(0, $total - $assigned);
$status = 'success';
if ($assigned === 0) {
$status = 'error';
} elseif ($assigned < $total) {
$status = 'partial';
}
if ($assigned === 0) {
$info = fxBibMsg('bib_v4_ajax_assign_none');
} elseif ($assigned >= $total) {
$info = fxBibMsg('bib_v4_ajax_assign_full', $assigned);
} else {
$info = fxBibMsg('bib_v4_ajax_assign_partial', $assigned, $remaining);
}
return renderBibView(
$epr_id,
0,
$strLangue,
'execute',
$exec,
fxBibTexte('bib_v4_ajax_title_assign'),
$info,
$status
);
}
function fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id = 0) {
$nbParticipants = fxCountBatchAssignUnits($epr_id, $participants);

View File

@ -7,7 +7,7 @@
* Constantes *
*
**************/
define('_VERSION_CODE', '4.72.732');
define('_VERSION_CODE', '4.72.733');
define('_DATE_CODE', '2026-07-07');
//MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');

View File

@ -0,0 +1,10 @@
-- MSIN-4433 — GO batch par paquets : libellé progression overlay
-- Idempotent. Exécuter après déploiement PHP/JS (ajax batch_apply_chunk).
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_batch_progress', 'fr', 'Assignation des dossards… %d / %d', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_batch_progress' 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_batch_progress', 'en', 'Assigning bib numbers… %d / %d', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_js_batch_progress' AND info_langue = 'en' AND info_prg = 'compte.php');