From 77799add6dc46b0178ff5ffbde19bdb85665ee03 Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 16 Jul 2026 09:51:34 -0400 Subject: [PATCH] =?UTF-8?q?MSIN-4445=20=E2=80=94=20Implement=20free=20assi?= =?UTF-8?q?gnment=20functionality=20for=20solo=20events=20without=20sequen?= =?UTF-8?q?ce=20creation.=20Add=20new=20actions=20for=20analysis,=20simula?= =?UTF-8?q?tion,=20and=20planning=20of=20free=20ranges.=20Update=20UI=20co?= =?UTF-8?q?mponents=20and=20JavaScript=20logic=20to=20support=20the=20new?= =?UTF-8?q?=20features,=20including=20feedback=20mechanisms=20for=20quanti?= =?UTF-8?q?ty=20validation.=20Increment=20version=20code=20to=204.72.828.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ajax_bib_range.php | 202 ++++++++++ css/style.css | 36 ++ js/v2/bib-assignments.js | 623 +++++++++++++++++++++++++++++- php/inc_fx_bib_global_free.php | 327 ++++++++++++++++ php/inc_fx_promoteur.php | 141 ++++++- php/inc_settings.php | 4 +- sql/MSIN-4445-bib-global-free.sql | 67 ++++ 7 files changed, 1393 insertions(+), 7 deletions(-) create mode 100644 php/inc_fx_bib_global_free.php create mode 100644 sql/MSIN-4445-bib-global-free.sql diff --git a/ajax_bib_range.php b/ajax_bib_range.php index 40bd0cd..addb40a 100644 --- a/ajax_bib_range.php +++ b/ajax_bib_range.php @@ -45,6 +45,10 @@ $arrBibAjaxActions = [ 'batch_global_gen_analysis', 'batch_global_gen_simulate', 'batch_global_gen_create', + 'batch_global_free_analysis', + 'batch_global_free_simulate', + 'batch_global_free_plan', + 'batch_apply_free_chunk', 'global_batch_panel', 'dist_print_panel', ]; @@ -700,6 +704,94 @@ if ($action == 'batch_global_gen_create') { exit; } +// MSIN-4445 — Analyse batch global libre (Début/Fin, sans séquence). +if ($action == 'batch_global_free_analysis') { + + $int_eve_id = (int)($_POST['eve_id'] ?? 0); + $intStart = (int)($_POST['start_bib'] ?? 0); + $intEnd = (int)($_POST['end_bib'] ?? 0); + list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); + $eprIds = json_decode($_POST['epr_ids'] ?? '[]', true); + $tabEprIds = fxBibParseGlobalBatchGeneratedEprIds(is_array($eprIds) ? $eprIds : [], $int_eve_id); + + if ($int_eve_id <= 0 || $intStart <= 0 || $intEnd < $intStart || $strSort1 === '' || empty($tabEprIds)) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), + ]); + exit; + } + + $tabPlan = fxBibBuildFreeRangePlan($int_eve_id, $tabEprIds, $intStart, $intEnd, $strSort1, $strSort2); + $tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($tabPlan, $intStart, $intEnd); + + echo json_encode([ + 'success' => true, + 'info' => $tabAnalysis['info'], + 'stats' => $tabAnalysis['stats'], + 'feedback' => $tabAnalysis['feedback'], + ]); + exit; +} + +// MSIN-4445 — Simulation batch global libre. +if ($action == 'batch_global_free_simulate') { + + $int_eve_id = (int)($_POST['eve_id'] ?? 0); + $intStart = (int)($_POST['start_bib'] ?? 0); + $intEnd = (int)($_POST['end_bib'] ?? 0); + list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); + $eprIds = json_decode($_POST['epr_ids'] ?? '[]', true); + $tabEprIds = fxBibParseGlobalBatchGeneratedEprIds(is_array($eprIds) ? $eprIds : [], $int_eve_id); + + if ($int_eve_id <= 0 || $intStart <= 0 || $intEnd < $intStart || $strSort1 === '' || empty($tabEprIds)) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), + ]); + exit; + } + + $tabPlan = fxBibBuildFreeRangePlan($int_eve_id, $tabEprIds, $intStart, $intEnd, $strSort1, $strSort2); + $result = fxBibSimulateGlobalBatchFree( + $int_eve_id, + $tabPlan, + $intStart, + $intEnd, + $strSort1, + $strSort2, + $strLangue + ); + + echo json_encode($result); + exit; +} + +// MSIN-4445 — Prépare les plans GO libres (aucune création de séquence). +if ($action == 'batch_global_free_plan') { + + $int_eve_id = (int)($_POST['eve_id'] ?? 0); + $intStart = (int)($_POST['start_bib'] ?? 0); + $intEnd = (int)($_POST['end_bib'] ?? 0); + list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); + $eprIds = json_decode($_POST['epr_ids'] ?? '[]', true); + $tabEprIds = fxBibParseGlobalBatchGeneratedEprIds(is_array($eprIds) ? $eprIds : [], $int_eve_id); + + if ($int_eve_id <= 0 || $intStart <= 0 || $intEnd < $intStart || $strSort1 === '' || empty($tabEprIds)) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), + ]); + exit; + } + + $tabPlan = fxBibBuildFreeRangePlan($int_eve_id, $tabEprIds, $intStart, $intEnd, $strSort1, $strSort2); + $prepare = fxBibPrepareFreeRangePlans($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue); + + echo json_encode($prepare); + exit; +} + if ($action == 'refresh_ranges') { $epr_id = intval($_POST['epr_id'] ?? $_GET['epr_id'] ?? 0); @@ -965,6 +1057,116 @@ if ($action == 'batch_apply_chunk') { echo json_encode($arrResponse); exit; } + +// MSIN-4445 — GO batch par paquets avec plage virtuelle (sans séquence en BD). +if ($action == 'batch_apply_free_chunk') { + + $epr_id = intval($_POST['epr_id'] ?? 0); + $intStart = (int)($_POST['start_bib'] ?? 0); + $intEnd = (int)($_POST['end_bib'] ?? 0); + list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); + $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; + } + + if ($epr_id <= 0 || $intStart <= 0 || $intEnd < $intStart) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), + ]); + exit; + } + + // Même filtre que le panneau : solo sans séquence. + $intEveId = (int)($_POST['eve_id'] ?? 0); + if ($intEveId > 0) { + $checkEpr = fxBibAssertSoloEpreuveForGlobalBatch($epr_id, $intEveId); + if (!$checkEpr['success']) { + echo json_encode($checkEpr); + exit; + } + if (fxBibEpreuveHasAnySequence($epr_id)) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_has_seq'), + ]); + exit; + } + } + + $strSessionKey = fxBibBatchExecSessionKey($epr_id) . '_free'; + + if ($blnBatchReset) { + $_SESSION[$strSessionKey] = []; + fxBibSaveEpreuveBatchSort($epr_id, $strSort1, $strSort2); + } elseif (!isset($_SESSION[$strSessionKey]) || !is_array($_SESSION[$strSessionKey])) { + $_SESSION[$strSessionKey] = []; + } + + $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); + + 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 = fxBibBuildVirtualRangesForPlan($intStart, $intEnd); + $execChunk = fxProcessBatchAssignments($epr_id, $tabChunk, $tabRanges, 'execute'); + + $_SESSION[$strSessionKey] = array_merge($_SESSION[$strSessionKey], $execChunk); + + $intRemaining = count(fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2)); + $intProcessed = max(0, $intTotalInitial - $intRemaining); + $blnComplete = ($intRemaining === 0); + + 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 // ===================== diff --git a/css/style.css b/css/style.css index 2f3a40e..c7e9002 100644 --- a/css/style.css +++ b/css/style.css @@ -1448,6 +1448,42 @@ ul.ms1-menu-compte li a:hover, ul.ms1-menu-compte li a:active { margin-bottom:4px; font-weight:600; } +/* MSIN-4445 — Assignation libre : Début / Qté / Fin */ +.bib-global-free-range__headers, +.bib-global-free-range__fields{ + display:grid; + grid-template-columns:minmax(72px, 1fr) minmax(72px, 1fr) auto minmax(72px, 1fr); + gap:6px 8px; + align-items:center; + max-width:420px; +} +.bib-global-free-range__headers{ + margin-bottom:2px; + font-size:11px; + text-transform:uppercase; + letter-spacing:.02em; + color:#6c757d; +} +.bib-global-free-range__or{ + text-align:center; + color:#9aa0a6; + font-size:12px; +} +.bib-global-free-range__feedback{ + margin-top:6px; +} +.bib-global-free-range__feedback--juste{ + color:#155724; + font-weight:600; +} +.bib-global-free-range__feedback--manquante{ + color:#856404; + font-weight:600; +} +.bib-global-free-range__feedback--supplementaire{ + color:#004085; + font-weight:600; +} .bib-global-epr--inactive{ opacity:0.55; background:#f1f3f5; diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js index 4816fa3..4fee8c0 100644 --- a/js/v2/bib-assignments.js +++ b/js/v2/bib-assignments.js @@ -218,9 +218,11 @@ return { sort1: '', sort2: '' }; } var s1 = panel.querySelector('.bib-global-batch-order') - || panel.querySelector('.bib-global-gen-batch-order'); + || panel.querySelector('.bib-global-gen-batch-order') + || panel.querySelector('.bib-global-free-batch-order'); var s2 = panel.querySelector('.bib-global-batch-order-2') - || panel.querySelector('.bib-global-gen-batch-order-2'); + || panel.querySelector('.bib-global-gen-batch-order-2') + || panel.querySelector('.bib-global-free-batch-order-2'); return { sort1: s1 ? s1.value : '', sort2: s2 ? s2.value : '' @@ -366,6 +368,11 @@ return body ? body.querySelector('.bib-global-batch--generated') : null; } + function bibGetGlobalBatchFreeSection() { + var body = bibGetGlobalBatchBody(); + return body ? body.querySelector('.bib-global-batch--free') : null; + } + function bibInvalidateGlobalBatchPanel() { var mount = document.getElementById('bib-global-batch-mount'); var panel = bibGetGlobalBatchPanel(); @@ -797,6 +804,10 @@ } initModuleBibTippy(mount); bibRemountActiveToolAfterRefresh('global_batch'); + var freeSection = bibGetGlobalBatchFreeSection(); + if (freeSection) { + bibBindGlobalFreeRange(freeSection); + } return panel; }) .finally(function () { @@ -1173,6 +1184,561 @@ }); } + // MSIN-4445 — Assignation libre (Début / Qté / Fin, sans séquence). + function bibCollectGlobalBatchFreeEprIds() { + var section = bibGetGlobalBatchFreeSection(); + if (!section) { + return []; + } + + var ids = []; + section.querySelectorAll('.bib-global-free-epr-select:checked').forEach(function (cb) { + var eprId = parseInt(cb.dataset.eprId || '0', 10); + if (eprId) { + ids.push(eprId); + } + }); + + return ids; + } + + function bibCollectGlobalBatchFreePending() { + var section = bibGetGlobalBatchFreeSection(); + if (!section) { + return 0; + } + + var total = 0; + section.querySelectorAll('.bib-global-free-epr-select:checked').forEach(function (cb) { + var block = cb.closest('.bib-global-epr'); + var pending = block + ? parseInt(block.dataset.pending || '0', 10) + : 0; + if (pending > 0) { + total += pending; + } + }); + + return total; + } + + function bibGetGlobalBatchFreeRangeFields(section) { + section = section || bibGetGlobalBatchFreeSection(); + if (!section) { + return null; + } + + var wrap = section.querySelector('.bib-global-free-range'); + if (!wrap) { + return null; + } + + return { + wrap: wrap, + 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') + }; + } + + function bibGetGlobalBatchFreeBounds(section) { + var fields = bibGetGlobalBatchFreeRangeFields(section); + if (!fields) { + return { start: 0, end: 0, qty: 0 }; + } + + var start = bibRangeParseInt(fields.start && fields.start.value); + var qty = bibRangeParseInt(fields.qty && fields.qty.value); + var end = bibRangeParseInt(fields.end && fields.end.value); + var pilot = (fields.wrap && fields.wrap.dataset.rangePilot) || ''; + + if (start > 0 && pilot === 'qty' && qty > 0) { + end = start + qty - 1; + } else if (start > 0 && pilot === 'end' && end >= start) { + qty = end - start + 1; + } else if (start > 0 && end >= start) { + qty = end - start + 1; + } else { + qty = 0; + if (end < start) { + end = 0; + } + } + + return { start: start, end: end, qty: qty }; + } + + function bibFormatGlobalFreeQtyFeedback(qty, pending) { + var diff = qty - pending; + if (qty <= 0) { + return ''; + } + if (diff === 0) { + return bibJs('jsGlobalFreeQtyJuste') || 'Quantité juste'; + } + if (diff > 0) { + return bibJsFmt('jsGlobalFreeQtySupplementaire', diff) + || (('Quantité supplémentaire : %d').replace('%d', String(diff))); + } + return bibJsFmt('jsGlobalFreeQtyManquante', Math.abs(diff)) + || (('Quantité manquante : %d').replace('%d', String(Math.abs(diff)))); + } + + function bibUpdateGlobalFreeQtyFeedback(section) { + var fields = bibGetGlobalBatchFreeRangeFields(section); + if (!fields || !fields.feedback) { + 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' + ); + + if (!msg) { + return; + } + + 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'); + } + } + + function bibSyncGlobalFreeRangeDerived(section) { + var fields = bibGetGlobalBatchFreeRangeFields(section); + if (!fields) { + return; + } + + var start = bibRangeParseInt(fields.start && fields.start.value); + var pilot = (fields.wrap && fields.wrap.dataset.rangePilot) || ''; + + if (start <= 0) { + if (fields.qty) { + fields.qty.value = ''; + fields.qty.disabled = true; + fields.qty.classList.remove('bib-field--active', 'bib-field--calc', 'bib-field--pick'); + fields.qty.classList.add('bib-field--idle'); + } + if (fields.end) { + fields.end.value = ''; + fields.end.disabled = true; + fields.end.classList.remove('bib-field--active', 'bib-field--calc', 'bib-field--pick'); + fields.end.classList.add('bib-field--idle'); + } + if (fields.wrap) { + fields.wrap.dataset.rangePilot = ''; + } + bibUpdateGlobalFreeQtyFeedback(section); + return; + } + + if (!pilot) { + if (fields.qty) { + fields.qty.disabled = false; + fields.qty.classList.remove('bib-field--idle', 'bib-field--active', 'bib-field--calc'); + fields.qty.classList.add('bib-field--pick'); + } + if (fields.end) { + fields.end.disabled = false; + fields.end.classList.remove('bib-field--idle', 'bib-field--active', 'bib-field--calc'); + fields.end.classList.add('bib-field--pick'); + } + bibUpdateGlobalFreeQtyFeedback(section); + return; + } + + if (pilot === 'qty') { + var qty = bibRangeParseInt(fields.qty && fields.qty.value); + if (fields.end) { + fields.end.disabled = true; + fields.end.classList.remove('bib-field--active', 'bib-field--pick', 'bib-field--idle'); + fields.end.classList.add('bib-field--calc'); + fields.end.value = qty > 0 ? String(start + qty - 1) : ''; + } + if (fields.qty) { + fields.qty.disabled = false; + fields.qty.classList.remove('bib-field--idle', 'bib-field--calc', 'bib-field--pick'); + fields.qty.classList.add('bib-field--active'); + } + } else if (pilot === 'end') { + var end = bibRangeParseInt(fields.end && fields.end.value); + if (fields.qty) { + fields.qty.disabled = true; + fields.qty.classList.remove('bib-field--active', 'bib-field--pick', 'bib-field--idle'); + fields.qty.classList.add('bib-field--calc'); + fields.qty.value = (end >= start) ? String(end - start + 1) : ''; + } + if (fields.end) { + fields.end.disabled = false; + fields.end.classList.remove('bib-field--idle', 'bib-field--calc', 'bib-field--pick'); + fields.end.classList.add('bib-field--active'); + } + } + + bibUpdateGlobalFreeQtyFeedback(section); + } + + function bibPickGlobalFreePilot(section, pilot) { + var fields = bibGetGlobalBatchFreeRangeFields(section); + if (!fields || !fields.wrap) { + return; + } + + var start = bibRangeParseInt(fields.start && fields.start.value); + if (start <= 0) { + return; + } + + fields.wrap.dataset.rangePilot = pilot; + bibSyncGlobalFreeRangeDerived(section); + + var target = pilot === 'qty' ? fields.qty : fields.end; + if (target && !target.disabled) { + target.focus(); + } + } + + function bibBindGlobalFreeRange(section) { + var fields = bibGetGlobalBatchFreeRangeFields(section); + if (!fields || !fields.wrap || fields.wrap.dataset.bound === '1') { + return; + } + fields.wrap.dataset.bound = '1'; + + if (fields.start) { + fields.start.addEventListener('input', function () { + bibSyncGlobalFreeRangeDerived(section); + updateGlobalBatchFreeAnalysis(); + }); + } + + if (fields.qty) { + fields.qty.addEventListener('focus', function () { + bibPickGlobalFreePilot(section, 'qty'); + }); + fields.qty.addEventListener('click', function () { + bibPickGlobalFreePilot(section, 'qty'); + }); + fields.qty.addEventListener('input', function () { + if (fields.wrap.dataset.rangePilot !== 'qty') { + fields.wrap.dataset.rangePilot = 'qty'; + } + bibSyncGlobalFreeRangeDerived(section); + updateGlobalBatchFreeAnalysis(); + }); + } + + if (fields.end) { + fields.end.addEventListener('focus', function () { + bibPickGlobalFreePilot(section, 'end'); + }); + fields.end.addEventListener('click', function () { + bibPickGlobalFreePilot(section, 'end'); + }); + fields.end.addEventListener('input', function () { + if (fields.wrap.dataset.rangePilot !== 'end') { + fields.wrap.dataset.rangePilot = 'end'; + } + bibSyncGlobalFreeRangeDerived(section); + updateGlobalBatchFreeAnalysis(); + }); + } + + bibSyncGlobalFreeRangeDerived(section); + } + + function bibAppendGlobalBatchFreeParams(body, section) { + var sortKeys = bibGetGlobalBatchSortKeys(section); + var bounds = bibGetGlobalBatchFreeBounds(section); + body = bibAppendBatchSortParams(body, sortKeys); + body += '&start_bib=' + encodeURIComponent(bounds.start); + body += '&end_bib=' + encodeURIComponent(bounds.end); + body += '&epr_ids=' + encodeURIComponent(JSON.stringify(bibCollectGlobalBatchFreeEprIds())); + return body; + } + + function updateGlobalBatchFreeAnalysis() { + var section = bibGetGlobalBatchFreeSection(); + if (!section) { + return Promise.resolve(); + } + + bibBindGlobalFreeRange(section); + + var infoEl = section.querySelector('.bib-global-free-batch__info'); + var eprIds = bibCollectGlobalBatchFreeEprIds(); + var bounds = bibGetGlobalBatchFreeBounds(section); + + bibUpdateGlobalFreeQtyFeedback(section); + + if (!infoEl) { + return Promise.resolve(); + } + + if (eprIds.length === 0) { + bibSetGlobalBatchInfo( + infoEl, + bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone'), + 'error' + ); + return Promise.resolve(null); + } + + if (bounds.start <= 0 || bounds.end < bounds.start) { + bibSetGlobalBatchInfo( + infoEl, + bibJs('jsGlobalFreePlanInvalid') || bibJs('jsFieldsRequired'), + 'error' + ); + return Promise.resolve(null); + } + + bibSetGlobalBatchInfo(infoEl, bibJs('jsLoaderWait') || '…', 'normal'); + + return fetch('/ajax_bib_range.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: bibAppendGlobalBatchFreeParams( + 'action=batch_global_free_analysis' + + '&eve_id=' + encodeURIComponent(moduleBib.dataset.eveId || ''), + section + ) + bibAjaxLangSuffix() + }) + .then(function (res) { return res.json(); }) + .then(function (data) { + if (!data.success) { + bibSetGlobalBatchInfo( + infoEl, + data.message || bibJs('jsErrGeneric'), + 'error' + ); + return data; + } + + var state = 'normal'; + if (data.feedback && data.feedback.status === 'manquante') { + state = 'warning'; + } + bibSetGlobalBatchInfo(infoEl, data.info || '', state); + return data; + }) + .catch(function () { + bibSetGlobalBatchInfo(infoEl, bibJs('jsErrGeneric'), 'error'); + return null; + }); + } + + function bibFetchBatchApplyFreeChunk(row, plan, sortKeys, opts) { + opts = opts || {}; + sortKeys = sortKeys || bibGetRowSortKeys(row); + var body = + 'action=batch_apply_free_chunk' + + '&epr_id=' + encodeURIComponent(row.dataset.eprId) + + '&eve_id=' + encodeURIComponent(moduleBib.dataset.eveId || '') + + '&start_bib=' + encodeURIComponent(plan.start || 0) + + '&end_bib=' + encodeURIComponent(plan.end || 0) + + bibAppendBatchSortParams('', sortKeys) + + '&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) { + if (!res.ok) { + throw new Error(bibJs('jsErrGeneric')); + } + return res.json(); + }); + } + + function bibRunBatchApplyFreeChunks(row, plan, sortKeys, options) { + options = options || {}; + var manageLoader = options.manageLoader !== false; + var onProgress = options.onProgress; + var totalInitial = options.totalInitial > 0 + ? options.totalInitial + : bibRowPendingBibs(row); + var batchReset = true; + var chunkGuard = 0; + var lastData = null; + + function runNext() { + chunkGuard += 1; + if (chunkGuard > 500) { + return Promise.reject(new Error(bibJs('jsErrGeneric'))); + } + + return bibFetchBatchApplyFreeChunk(row, plan, sortKeys, { + totalInitial: totalInitial, + batchReset: batchReset + }).then(function (data) { + batchReset = false; + lastData = data; + + if (!data || !data.success) { + throw new Error((data && data.message) || bibJs('jsErrGeneric')); + } + + if (typeof onProgress === 'function') { + onProgress(data.processed || 0); + } + + if (data.complete) { + return data; + } + + return runNext(); + }); + } + + if (manageLoader) { + bibBatchProgressShow(0, totalInitial); + } + + return runNext().then(function (data) { + return data || lastData || { success: true, assigned_total: 0, total_initial: totalInitial }; + }); + } + + function bibRunGlobalBatchFreeGo() { + var section = bibGetGlobalBatchFreeSection(); + if (!section) { + return; + } + + var eprIds = bibCollectGlobalBatchFreeEprIds(); + var bounds = bibGetGlobalBatchFreeBounds(section); + var sortKeys = bibGetGlobalBatchSortKeys(section); + + if (eprIds.length === 0) { + alert(bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone')); + return; + } + + if (bounds.start <= 0 || bounds.end < bounds.start) { + alert(bibJs('jsGlobalFreePlanInvalid') || bibJs('jsFieldsRequired')); + return; + } + + fetch('/ajax_bib_range.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: bibAppendGlobalBatchFreeParams( + 'action=batch_global_free_plan' + + '&eve_id=' + encodeURIComponent(moduleBib.dataset.eveId || ''), + section + ) + bibAjaxLangSuffix() + }) + .then(function (res) { + if (!res.ok) { + throw new Error(bibJs('jsErrGeneric') || 'Erreur'); + } + return res.json().catch(function () { + throw new Error(bibJs('jsServerError') || bibJs('jsErrGeneric') || 'Erreur serveur'); + }); + }) + .then(function (planData) { + if (!planData || !planData.success || !Array.isArray(planData.plans) || planData.plans.length === 0) { + throw new Error((planData && planData.message) || bibJs('jsErrGeneric') || 'Erreur'); + } + + var plans = planData.plans; + var globalTotal = (planData.stats && planData.stats.nb_participants) + ? planData.stats.nb_participants + : 1; + var completedOffset = 0; + var runResults = []; + + bibBatchProgressShow(0, globalTotal); + + var chain = Promise.resolve(); + + plans.forEach(function (plan) { + chain = chain.then(function () { + var row = moduleBib.querySelector('.epr-row[data-epr-id="' + plan.epr_id + '"]'); + if (!row) { + throw new Error( + bibJs('jsGlobalGenEprMissing') + || bibJs('jsErrGeneric') + || 'Épreuve introuvable — rechargez la page.' + ); + } + + var pendingBefore = bibRowPendingBibs(row); + var offset = completedOffset; + + return bibRunBatchApplyFreeChunks(row, plan, sortKeys, { + manageLoader: false, + totalInitial: pendingBefore, + onProgress: function (processed) { + bibBatchProgressShow(offset + processed, globalTotal); + } + }).then(function (data) { + runResults.push({ + epr_id: plan.epr_id, + assigned: data.assigned_total || 0, + total: data.total_initial || pendingBefore + }); + completedOffset = offset + pendingBefore; + }); + }); + }); + + return chain.then(function () { + bibBatchProgressShow( + globalTotal, + globalTotal, + bibJs('jsGlobalBatchFinalizing') || bibJs('jsLoaderWait') + ); + + var refreshChain = Promise.resolve(); + runResults.forEach(function (result) { + refreshChain = refreshChain.then(function () { + var row = moduleBib.querySelector('.epr-row[data-epr-id="' + result.epr_id + '"]'); + if (!row) { + return null; + } + return bibAfterBatchGoRefreshOnly(row, false, { + loader: false, + skipAnomalies: true + }); + }); + }); + + return refreshChain.then(function () { + if (typeof refreshBibAnomaliesPanel === 'function') { + return refreshBibAnomaliesPanel(); + } + }).then(function () { + bibInvalidateGlobalBatchPanel(); + bibFinishGlobalBatchGo(runResults); + }); + }); + }) + .catch(function (err) { + alert(err.message || bibJs('jsErrGeneric')); + }) + .finally(function () { + bibBatchProgressForceHide(); + }); + } + function bibRunGlobalBatchGo() { var section = bibGetGlobalBatchExistingSection(); if (!section) { @@ -1408,13 +1974,59 @@ if (e.target.closest('.btn-go-global-gen-batch')) { e.preventDefault(); bibRunGlobalBatchGeneratedGo(); + return; + } + + if (e.target.closest('.btn-simuler-global-free-batch')) { + e.preventDefault(); + var freeSection = bibGetGlobalBatchFreeSection(); + var freeEprIds = bibCollectGlobalBatchFreeEprIds(); + var freeBounds = bibGetGlobalBatchFreeBounds(freeSection); + + if (freeEprIds.length === 0) { + alert(bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone')); + return; + } + + if (freeBounds.start <= 0 || freeBounds.end < freeBounds.start) { + alert(bibJs('jsGlobalFreePlanInvalid') || bibJs('jsFieldsRequired')); + return; + } + + bibFetch('/ajax_bib_range.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: bibAppendGlobalBatchFreeParams( + 'action=batch_global_free_simulate' + + '&eve_id=' + encodeURIComponent(moduleBib.dataset.eveId || ''), + freeSection + ) + bibAjaxLangSuffix() + }) + .then(function (res) { return res.json(); }) + .then(function (data) { + if (!data.success) { + alert(data.message || bibJs('jsErrGeneric')); + return; + } + + bibInsertGlobalSimView(data.html); + }); + + return; + } + + if (e.target.closest('.btn-go-global-free-batch')) { + e.preventDefault(); + bibRunGlobalBatchFreeGo(); } }); moduleBib.addEventListener('change', function (e) { if (!e.target.matches( '.bib-global-epr-select, .bib-global-range-select, .bib-global-batch-order, .bib-global-batch-order-2,' - + ' .bib-global-gen-epr-select, .bib-global-gen-batch-order, .bib-global-gen-batch-order-2, .bib-global-gen-start-bib' + + ' .bib-global-gen-epr-select, .bib-global-gen-batch-order, .bib-global-gen-batch-order-2, .bib-global-gen-start-bib,' + + ' .bib-global-free-epr-select, .bib-global-free-batch-order, .bib-global-free-batch-order-2,' + + ' .bib-global-free-start, .bib-global-free-qty, .bib-global-free-end' )) { return; } @@ -1441,6 +2053,11 @@ if (e.target.closest('.bib-global-batch--generated')) { updateGlobalBatchGeneratedAnalysis(); + return; + } + + if (e.target.closest('.bib-global-batch--free')) { + updateGlobalBatchFreeAnalysis(); } }); diff --git a/php/inc_fx_bib_global_free.php b/php/inc_fx_bib_global_free.php new file mode 100644 index 0000000..591cbbe --- /dev/null +++ b/php/inc_fx_bib_global_free.php @@ -0,0 +1,327 @@ + 'juste', + 'amount' => 0, + 'qty' => $intQty, + 'pending' => $intPending, + ]; + } + + if ($intDiff > 0) { + return [ + 'status' => 'supplementaire', + 'amount' => $intDiff, + 'qty' => $intQty, + 'pending' => $intPending, + ]; + } + + return [ + 'status' => 'manquante', + 'amount' => abs($intDiff), + 'qty' => $intQty, + 'pending' => $intPending, + ]; +} + +/** Libellé Info pour le feedback quantité libre. */ +function fxBibFreeQtyFeedbackMessage(array $tabFeedback) { + $strStatus = $tabFeedback['status'] ?? ''; + $intAmount = (int)($tabFeedback['amount'] ?? 0); + + if ($strStatus === 'juste') { + return fxBibMsg('bib_v4_global_batch_free_qty_juste'); + } + + if ($strStatus === 'supplementaire') { + return fxBibMsg('bib_v4_global_batch_free_qty_supplementaire', $intAmount); + } + + if ($strStatus === 'manquante') { + return fxBibMsg('bib_v4_global_batch_free_qty_manquante', $intAmount); + } + + return ''; +} + +/** + * 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. + * + * @return array[] [{epr_id, start, end, nb_pending, skip}] + */ +function fxBibBuildFreeRangePlan($int_eve_id, $tabEprIds, $intStart, $intEnd, $strSort1, $strSort2) { + $int_eve_id = (int)$int_eve_id; + $intStart = (int)$intStart; + $intEnd = (int)$intEnd; + $tabEprIds = fxBibParseGlobalBatchGeneratedEprIds($tabEprIds, $int_eve_id); + $intCursor = $intStart; + $tabPlan = []; + + foreach ($tabEprIds as $epr_id) { + $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); + $intPending = count($participants); + + if ($intPending <= 0) { + $tabPlan[] = [ + 'epr_id' => $epr_id, + 'start' => 0, + 'end' => 0, + 'nb_pending' => 0, + 'skip' => true, + ]; + continue; + } + + $intRemaining = $intEnd - $intCursor + 1; + if ($intRemaining <= 0) { + $tabPlan[] = [ + 'epr_id' => $epr_id, + 'start' => 0, + 'end' => 0, + 'nb_pending' => $intPending, + 'skip' => true, + ]; + continue; + } + + $intTake = min($intPending, $intRemaining); + $intRangeStart = $intCursor; + $intRangeEnd = $intCursor + $intTake - 1; + + $tabPlan[] = [ + 'epr_id' => $epr_id, + 'start' => $intRangeStart, + 'end' => $intRangeEnd, + 'nb_pending' => $intPending, + 'skip' => false, + ]; + + $intCursor = $intRangeEnd + 1; + } + + return $tabPlan; +} + +/** + * Valide le plan libre (plage valide + au moins une assignation possible). + * Pas de contrôle de chevauchement ni d'insertion de séquence (MSIN-4445). + */ +function fxBibValidateFreeRangePlan($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue = 'fr') { + $int_eve_id = (int)$int_eve_id; + $intStart = (int)$intStart; + $intEnd = (int)$intEnd; + + if ($intStart <= 0 || $intEnd < $intStart) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), + ]; + } + + if (!is_array($tabPlan) || empty($tabPlan)) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), + ]; + } + + $blnHasAssignable = false; + + foreach ($tabPlan as $item) { + if (!empty($item['skip'])) { + continue; + } + + $epr_id = (int)($item['epr_id'] ?? 0); + $intItemStart = (int)($item['start'] ?? 0); + $intItemEnd = (int)($item['end'] ?? 0); + + if ($epr_id <= 0 || $intItemStart <= 0 || $intItemEnd < $intItemStart) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), + ]; + } + + if (fxBibEpreuveHasAnySequence($epr_id)) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_has_seq'), + ]; + } + + $blnHasAssignable = true; + } + + if (!$blnHasAssignable) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_no_pending'), + ]; + } + + return ['success' => true]; +} + +/** Stats agrégées + feedback quantité pour l'analyse libre. */ +function fxBibBuildGlobalBatchFreeAnalysis($tabPlan, $intStart, $intEnd) { + $intStart = (int)$intStart; + $intEnd = (int)$intEnd; + $intQty = ($intStart > 0 && $intEnd >= $intStart) ? ($intEnd - $intStart + 1) : 0; + $tabStats = fxBibBuildGlobalBatchGeneratedAnalysisStats($tabPlan); + + // MSIN-4445 — Feedback vs total à assigner (y compris épreuves non couvertes par la plage). + $intPendingAll = 0; + foreach ($tabPlan as $item) { + $intPendingAll += (int)($item['nb_pending'] ?? 0); + } + + $tabFeedback = fxBibFreeQtyFeedback($intQty, $intPendingAll); + + return [ + 'stats' => $tabStats, + 'feedback' => $tabFeedback, + 'info' => fxBibMsg( + 'bib_v4_ajax_global_free_analysis', + $tabStats['nb_epreuves'], + $intStart, + $intEnd, + $intPendingAll, + fxBibFreeQtyFeedbackMessage($tabFeedback) + ), + ]; +} + +/** + * Simulation globale libre (plages virtuelles, aucune écriture BD). + * + * @return array{success:bool, html?:string, info?:string, message?:string} + */ +function fxBibSimulateGlobalBatchFree($int_eve_id, $tabPlan, $intStart, $intEnd, $strSort1, $strSort2, $strLangue) { + $check = fxBibValidateFreeRangePlan($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue); + + if (!$check['success']) { + return $check; + } + + $htmlParts = []; + $intParticipants = 0; + $intBibs = 0; + + foreach ($tabPlan as $item) { + if (!empty($item['skip'])) { + continue; + } + + $epr_id = (int)$item['epr_id']; + $intItemStart = (int)$item['start']; + $intItemEnd = (int)$item['end']; + $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); + $tabRanges = fxBibBuildVirtualRangesForPlan($intItemStart, $intItemEnd); + $simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation'); + + $intNbPending = count($participants); + $intParticipants += $intNbPending; + $intBibs += max(0, $intItemEnd - $intItemStart + 1); + + $tabEpreuve = fxGetEpreuve($epr_id); + $strEprLabel = $tabEpreuve ? fxBibEpreuveDisplayName($tabEpreuve, $strLangue) : ('#' . $epr_id); + + $strRangeLabel = fxBibMsg( + 'bib_v4_global_batch_gen_range_preview', + $intItemStart, + $intItemEnd, + $intNbPending + ); + + $htmlParts[] = '
' + . '

' . fxBibEsc($strEprLabel) . '

' + . '

' . fxBibEsc($strRangeLabel) . '

' + . renderBibView( + $epr_id, + 0, + $strLangue, + 'simulation', + $simu, + fxBibTexte('bib_v4_ajax_title_simulation'), + fxBibMsg( + 'bib_v4_ajax_sim_summary', + max(0, $intItemEnd - $intItemStart + 1), + $intNbPending, + max(0, $intNbPending - count($simu)) + ) + ) + . '
'; + } + + $tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($tabPlan, $intStart, $intEnd); + $info = fxBibMsg( + 'bib_v4_ajax_global_free_sim_summary', + $tabAnalysis['stats']['nb_epreuves'], + $intBibs, + (int)($tabAnalysis['feedback']['pending'] ?? $intParticipants), + fxBibFreeQtyFeedbackMessage($tabAnalysis['feedback']) + ); + + $html = fxBibWrapGlobalSimView($info, implode('', $htmlParts), 'bib-global-sim-view--free'); + + return [ + 'success' => true, + 'html' => $html, + 'info' => $info, + 'feedback' => $tabAnalysis['feedback'], + ]; +} + +/** + * Prépare les plans GO libres (sans INSERT séquence). + * + * @return array{success:bool, plans?:array, stats?:array, feedback?:array, message?:string} + */ +function fxBibPrepareFreeRangePlans($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue = 'fr') { + $check = fxBibValidateFreeRangePlan($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue); + + if (!$check['success']) { + return $check; + } + + $tabPlans = []; + + foreach ($tabPlan as $item) { + if (!empty($item['skip'])) { + continue; + } + + $tabPlans[] = [ + 'epr_id' => (int)$item['epr_id'], + 'start' => (int)$item['start'], + 'end' => (int)$item['end'], + ]; + } + + $tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($tabPlan, $intStart, $intEnd); + + return [ + 'success' => true, + 'plans' => $tabPlans, + 'stats' => $tabAnalysis['stats'], + 'feedback' => $tabAnalysis['feedback'], + ]; +} diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index 41d4aa3..a55fd7d 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -3444,6 +3444,15 @@ function fxBibStaticFallback($clef) { 'bib_v4_global_batch_go_complete_title' => ['fr' => 'Assignation terminée', 'en' => 'Assignment complete'], 'bib_v4_global_batch_go_complete_summary' => ['fr' => '%d dossard(s) assigné(s) sur %d · %d épreuve(s)', 'en' => '%d bib(s) assigned of %d · %d race(s)'], 'bib_v4_global_batch_go_complete_reload' => ['fr' => 'OK — Recharger la page', 'en' => 'OK — Reload page'], + // MSIN-4445 — Assignation libre. + 'bib_v4_global_batch_free_title' => ['fr' => 'Assignation libre', 'en' => 'Free assignment'], + 'bib_v4_global_batch_free_hint' => ['fr' => 'Épreuves solo sans séquence : choisissez Début et Fin (ou Qté). Les dossards sont assignés sans créer de séquence. Le Go reste disponible même si la quantité n\'est pas juste.', 'en' => 'Solo races without sequences: choose Start and End (or Qty). Bibs are assigned without creating a sequence. Go stays available even if the quantity is not exact.'], + '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_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'], 'bib_v4_batch_order_2' => ['fr' => 'Tri secondaire', 'en' => 'Secondary sort'], 'bib_v4_batch_order_none' => ['fr' => '— Aucun —', 'en' => '— None —'], 'bib_v4_batch_sort_group_fixed' => ['fr' => 'Critères fixes', 'en' => 'Fixed criteria'], @@ -3701,6 +3710,11 @@ function fxBibModuleJsDataAttrs() { 'global-batch-complete-title' => 'bib_v4_global_batch_go_complete_title', 'global-batch-complete-summary' => 'bib_v4_global_batch_go_complete_summary', 'global-batch-complete-reload' => 'bib_v4_global_batch_go_complete_reload', + // MSIN-4445 + '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-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', 'assign-full' => 'bib_v4_ajax_assign_full', @@ -4660,6 +4674,8 @@ function renderBibToolNav($int_eve_id, $tabEpreuves, $strLangue = 'fr') { // MSIN-4443 — Assignation globale sans séquence (module dédié). require_once __DIR__ . '/inc_fx_bib_global_generated.php'; +// MSIN-4445 — Assignation globale libre (sans création de séquence). +require_once __DIR__ . '/inc_fx_bib_global_free.php'; /** * MSIN-4436 / MSIN-4443 — Coquille assignation globale (chargement différé à l'ouverture). @@ -4952,14 +4968,132 @@ function renderBibGlobalBatchPanelGenerated($int_eve_id, $tabEpreuves, $strLangu } /** - * MSIN-4436 / MSIN-4443 — Panneau assignation globale (deux blocs indépendants). + * MSIN-4445 — Bloc assignation libre (Début/Qté/Fin, sans création de séquence). + */ +function renderBibGlobalBatchPanelFree($int_eve_id, $tabEpreuves, $strLangue) { + $int_eve_id = (int)$int_eve_id; + $tabSolo = fxBibGetSoloEpreuvesWithoutSequence($int_eve_id, $tabEpreuves); + + if (empty($tabSolo)) { + return ''; + } + + $tabGlobalSort = fxBibGetSortOptionsForEpreuve(0, $int_eve_id, $strLangue, false, false); + + ob_start(); + echo fxBibGlobalBatchSectionOpen('free', 'bib_v4_global_batch_free_title', $strLangue); + ?> +

+

+ +
+
+
+
+ +
+
+
+ + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ + +
+ +
+ + +
+ +

+ +
+ + + + + + + + + + + + +
+ diff --git a/php/inc_settings.php b/php/inc_settings.php index 5f49c70..01dec62 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -7,8 +7,8 @@ * Constantes * * **************/ -define('_VERSION_CODE', '4.72.827'); -define('_DATE_CODE', '2026-07-15'); +define('_VERSION_CODE', '4.72.828'); +define('_DATE_CODE', '2026-07-16'); //MSIN-4290 define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe'); diff --git a/sql/MSIN-4445-bib-global-free.sql b/sql/MSIN-4445-bib-global-free.sql new file mode 100644 index 0000000..59c9b34 --- /dev/null +++ b/sql/MSIN-4445-bib-global-free.sql @@ -0,0 +1,67 @@ +-- MSIN-4445 — Assignation libre (globale solo, sans création de séquence) +-- Idempotent. Exécuter après déploiement PHP/JS/CSS. +-- Prérequis : MSIN-4443 (assignation globale sans séquence) déjà en place. + +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_global_batch_free_title', 'fr', 'Assignation libre', 'Assigne des dossards dans une plage Début/Fin sans créer de séquence.', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_title' 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_global_batch_free_title', 'en', 'Free assignment', 'Assigns bibs in a Start/End range without creating a sequence.', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_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_global_batch_free_hint', 'fr', 'Épreuves solo sans séquence : choisissez Début et Fin (ou Qté). Les dossards sont assignés sans créer de séquence. Le Go reste disponible même si la quantité n''est pas juste.', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_hint' 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_global_batch_free_hint', 'en', 'Solo races without sequences: choose Start and End (or Qty). Bibs are assigned without creating a sequence. Go stays available even if the quantity is not exact.', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_hint' 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_global_batch_free_qty_juste', 'fr', 'Quantité juste', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_qty_juste' 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_global_batch_free_qty_juste', 'en', 'Exact quantity', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_qty_juste' 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_global_batch_free_qty_manquante', 'fr', 'Quantité manquante : %d', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_qty_manquante' 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_global_batch_free_qty_manquante', 'en', 'Missing quantity: %d', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_qty_manquante' 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_global_batch_free_qty_supplementaire', 'fr', 'Quantité supplémentaire : %d', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_qty_supplementaire' 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_global_batch_free_qty_supplementaire', 'en', 'Extra quantity: %d', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_qty_supplementaire' 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_global_batch_free_plan_invalid', 'fr', 'Sélection invalide : Début, Fin (ou Qté) et au moins une épreuve avec inscriptions à assigner.', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_plan_invalid' 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_global_batch_free_plan_invalid', 'en', 'Invalid selection: Start, End (or Qty) and at least one race with registrations to assign.', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_global_batch_free_plan_invalid' 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_global_free_analysis', 'fr', '%d épreuve(s) · plage %d → %d · %d à assigner · %s', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_global_free_analysis' 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_global_free_analysis', 'en', '%d race(s) · range %d → %d · %d to assign · %s', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_global_free_analysis' 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_global_free_sim_summary', 'fr', '%d épreuve(s) · %d dossards · %d à assigner · %s', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_global_free_sim_summary' 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_global_free_sim_summary', 'en', '%d race(s) · %d bibs · %d to assign · %s', '', 'compte.php', '', 0, 1, '', '', '', NOW() +FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_ajax_global_free_sim_summary' AND info_langue = 'en' AND info_prg = 'compte.php');