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:
@ -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
|
||||
// =====================
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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');
|
||||
|
||||
10
sql/MSIN-4433-bib-batch-chunk.sql
Normal file
10
sql/MSIN-4433-bib-batch-chunk.sql
Normal 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');
|
||||
Reference in New Issue
Block a user