diff --git a/ajax_bib_range.php b/ajax_bib_range.php index b63389a..82ce333 100644 --- a/ajax_bib_range.php +++ b/ajax_bib_range.php @@ -42,6 +42,9 @@ $arrBibAjaxActions = [ 'anomalies', 'batch_global_analysis', 'batch_global_simulate', + 'batch_global_gen_analysis', + 'batch_global_gen_simulate', + 'batch_global_gen_create', 'global_batch_panel', 'dist_print_panel', ]; @@ -418,7 +421,7 @@ if ($action == 'global_batch_panel') { if ($html === '') { echo json_encode([ 'success' => false, - 'message' => fxBibMsg('bib_v4_global_batch_need_two'), + 'message' => fxBibMsg('bib_v4_global_batch_unavailable'), ]); exit; } @@ -578,6 +581,128 @@ if ($action == 'batch_global_simulate') { exit; } +// MSIN-4443 — Analyse batch global sans séquence (plages générées). +if ($action == 'batch_global_gen_analysis') { + + $int_eve_id = (int)($_POST['eve_id'] ?? 0); + $intStartBib = (int)($_POST['start_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 || $intStartBib <= 0 || $strSort1 === '' || empty($tabEprIds)) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'), + ]); + exit; + } + + $tabPlan = fxBibBuildGeneratedRangePlan($int_eve_id, $tabEprIds, $intStartBib, $strSort1, $strSort2); + $check = fxBibValidateGeneratedRangePlan($int_eve_id, $tabPlan, $strLangue); + + if (!$check['success']) { + echo json_encode($check); + exit; + } + + $tabStats = fxBibBuildGlobalBatchGeneratedAnalysisStats($tabPlan); + $intRangeStart = $intStartBib; + $intRangeEnd = $intStartBib - 1; + + foreach ($tabPlan as $item) { + if (!empty($item['skip'])) { + continue; + } + if ($intRangeEnd < (int)$item['start']) { + $intRangeEnd = (int)$item['end']; + } else { + $intRangeEnd = (int)$item['end']; + } + } + + $info = fxBibMsg( + 'bib_v4_ajax_global_gen_analysis', + $tabStats['nb_epreuves'], + $intRangeStart, + $intRangeEnd, + $tabStats['nb_participants'] + ); + + echo json_encode([ + 'success' => true, + 'info' => $info, + 'stats' => $tabStats, + ]); + exit; +} + +// MSIN-4443 — Simulation batch global sans séquence. +if ($action == 'batch_global_gen_simulate') { + + $int_eve_id = (int)($_POST['eve_id'] ?? 0); + $intStartBib = (int)($_POST['start_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 || $intStartBib <= 0 || $strSort1 === '' || empty($tabEprIds)) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'), + ]); + exit; + } + + $tabPlan = fxBibBuildGeneratedRangePlan($int_eve_id, $tabEprIds, $intStartBib, $strSort1, $strSort2); + $result = fxBibSimulateGlobalBatchGenerated($int_eve_id, $tabPlan, $strSort1, $strSort2, $strLangue); + + echo json_encode($result); + exit; +} + +// MSIN-4443 — Création des séquences générées (phase 1 du GO). +if ($action == 'batch_global_gen_create') { + + $int_eve_id = (int)($_POST['eve_id'] ?? 0); + $intStartBib = (int)($_POST['start_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 || $intStartBib <= 0 || $strSort1 === '' || empty($tabEprIds)) { + echo json_encode([ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'), + ]); + exit; + } + + $tabPlan = fxBibBuildGeneratedRangePlan($int_eve_id, $tabEprIds, $intStartBib, $strSort1, $strSort2); + $check = fxBibValidateGeneratedRangePlan($int_eve_id, $tabPlan, $strLangue); + + if (!$check['success']) { + echo json_encode($check); + exit; + } + + $create = fxBibCreateGeneratedRanges($int_eve_id, $tabPlan, $strLangue); + + if (!$create['success']) { + echo json_encode($create); + exit; + } + + $tabStats = fxBibBuildGlobalBatchGeneratedAnalysisStats($tabPlan); + + echo json_encode([ + 'success' => true, + 'plans' => $create['plans'], + 'stats' => $tabStats, + ]); + exit; +} + if ($action == 'refresh_ranges') { $epr_id = intval($_POST['epr_id'] ?? $_GET['epr_id'] ?? 0); diff --git a/css/style.css b/css/style.css index da87bf1..ee84c79 100644 --- a/css/style.css +++ b/css/style.css @@ -1398,6 +1398,37 @@ ul.ms1-menu-compte li a:hover, ul.ms1-menu-compte li a:active { padding-top:12px; border-top:1px solid #d8e3ef; } +.bib-global-batch__section{ + margin-bottom:0; +} +.bib-global-batch__section-title{ + font-size:1rem; + font-weight:600; + margin:0 0 10px; +} +.bib-global-batch__section-sep{ + margin:20px 0; + border-color:#d8e3ef; +} +.bib-global-batch__start-bib{ + max-width:220px; +} +.bib-global-batch__start-bib-label{ + display:block; + margin-bottom:4px; + font-weight:600; +} +.bib-global-epr--inactive{ + opacity:0.55; + background:#f1f3f5; +} +.bib-global-epr--inactive .bib-global-epr__name, +.bib-global-epr--inactive .bib-global-epr__pending{ + color:#6c757d; +} +.bib-global-sim-section__range{ + margin-bottom:8px; +} .bib-global-batch--unavailable{ background:#f8f9fa; border-style:dashed; diff --git a/js/v2/bib-assignments.js b/js/v2/bib-assignments.js index 0c6e974..6ac51bb 100644 --- a/js/v2/bib-assignments.js +++ b/js/v2/bib-assignments.js @@ -217,8 +217,10 @@ if (!panel) { return { sort1: '', sort2: '' }; } - var s1 = panel.querySelector('.bib-global-batch-order'); - var s2 = panel.querySelector('.bib-global-batch-order-2'); + var s1 = panel.querySelector('.bib-global-batch-order') + || panel.querySelector('.bib-global-gen-batch-order'); + var s2 = panel.querySelector('.bib-global-batch-order-2') + || panel.querySelector('.bib-global-gen-batch-order-2'); return { sort1: s1 ? s1.value : '', sort2: s2 ? s2.value : '' @@ -340,6 +342,36 @@ return document.getElementById('bib-global-batch'); } + function bibGetGlobalBatchExistingSection() { + var panel = bibGetGlobalBatchPanel(); + return panel ? panel.querySelector('.bib-global-batch--existing') : null; + } + + function bibGetGlobalBatchGeneratedSection() { + var panel = bibGetGlobalBatchPanel(); + return panel ? panel.querySelector('.bib-global-batch--generated') : null; + } + + function bibInvalidateGlobalBatchPanel() { + var mount = document.getElementById('bib-global-batch-mount'); + var panel = bibGetGlobalBatchPanel(); + + if (panel) { + panel.removeAttribute('data-global-batch-loaded'); + panel.setAttribute('data-global-batch-deferred', '1'); + } + + if (mount && panel) { + mount.innerHTML = '' + + '
' + + '
' + + '
'; + } + } + // MSIN-4440 — Grille outils : un panneau de détail, contenu déplacé depuis #bib-tool-store. var bibActiveTool = null; var bibToolContentSlots = {}; @@ -590,6 +622,7 @@ if (toolId === 'global_batch') { updateGlobalBatchAnalysis(); + updateGlobalBatchGeneratedAnalysis(); } bibSyncToolNavBadges(toolId); @@ -608,6 +641,7 @@ if (toolId === 'global_batch') { updateGlobalBatchAnalysis(); + updateGlobalBatchGeneratedAnalysis(); } } @@ -684,6 +718,7 @@ } bibGlobalBatchSetToggleState(toggle, true); updateGlobalBatchAnalysis(); + updateGlobalBatchGeneratedAnalysis(); } function bibSetGlobalBatchInfo(infoEl, text, state) { @@ -706,13 +741,13 @@ } function bibCollectGlobalBatchPlans() { - var bibGlobalBatchPanel = bibGetGlobalBatchPanel(); - if (!bibGlobalBatchPanel) { + var section = bibGetGlobalBatchExistingSection(); + if (!section) { return []; } var plans = []; - bibGlobalBatchPanel.querySelectorAll('.bib-global-epr').forEach(function (eprBlock) { + section.querySelectorAll('.bib-global-epr').forEach(function (eprBlock) { var eprCb = eprBlock.querySelector('.bib-global-epr-select'); if (!eprCb || !eprCb.checked) { return; @@ -737,14 +772,14 @@ } function updateGlobalBatchAnalysis() { - var bibGlobalBatchPanel = bibGetGlobalBatchPanel(); - if (!bibGlobalBatchPanel) { + var section = bibGetGlobalBatchExistingSection(); + if (!section || !section.querySelector('.bib-global-epr-select')) { return Promise.resolve(); } var plans = bibCollectGlobalBatchPlans(); - var infoEl = bibGlobalBatchPanel.querySelector('.bib-global-batch__info'); - var sortKeys = bibGetGlobalBatchSortKeys(bibGlobalBatchPanel); + var infoEl = section.querySelector('.bib-global-batch__info'); + var sortKeys = bibGetGlobalBatchSortKeys(section); if (!infoEl) { return Promise.resolve(); @@ -802,14 +837,276 @@ }); } + function bibCollectGlobalBatchGeneratedEprIds() { + var section = bibGetGlobalBatchGeneratedSection(); + if (!section) { + return []; + } + + var ids = []; + section.querySelectorAll('.bib-global-gen-epr-select:checked').forEach(function (cb) { + var eprId = parseInt(cb.dataset.eprId || '0', 10); + if (eprId) { + ids.push(eprId); + } + }); + + return ids; + } + + function bibGetGlobalBatchGeneratedStartBib() { + var section = bibGetGlobalBatchGeneratedSection(); + if (!section) { + return 0; + } + + var inp = section.querySelector('.bib-global-gen-start-bib'); + if (!inp) { + return 0; + } + + return parseInt(inp.value || '0', 10) || 0; + } + + function bibAppendGlobalBatchGeneratedParams(body, section) { + var sortKeys = bibGetGlobalBatchSortKeys(section); + body = bibAppendBatchSortParams(body, sortKeys); + body += '&start_bib=' + encodeURIComponent(bibGetGlobalBatchGeneratedStartBib()); + body += '&epr_ids=' + encodeURIComponent(JSON.stringify(bibCollectGlobalBatchGeneratedEprIds())); + return body; + } + + function updateGlobalBatchGeneratedAnalysis() { + var section = bibGetGlobalBatchGeneratedSection(); + if (!section) { + return Promise.resolve(); + } + + var infoEl = section.querySelector('.bib-global-gen-batch__info'); + var eprIds = bibCollectGlobalBatchGeneratedEprIds(); + var startBib = bibGetGlobalBatchGeneratedStartBib(); + + if (!infoEl) { + return Promise.resolve(); + } + + if (eprIds.length === 0) { + bibSetGlobalBatchInfo( + infoEl, + bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone'), + 'error' + ); + return Promise.resolve(null); + } + + if (startBib <= 0) { + bibSetGlobalBatchInfo( + infoEl, + bibJs('jsGlobalGenNoStart') || 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: bibAppendGlobalBatchGeneratedParams( + 'action=batch_global_gen_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; + } + + bibSetGlobalBatchInfo(infoEl, data.info || '', 'normal'); + return data; + }) + .catch(function () { + bibSetGlobalBatchInfo(infoEl, bibJs('jsErrGeneric'), 'error'); + return null; + }); + } + + function bibRunGlobalBatchGeneratedGo() { + var section = bibGetGlobalBatchGeneratedSection(); + if (!section) { + return; + } + + var eprIds = bibCollectGlobalBatchGeneratedEprIds(); + var startBib = bibGetGlobalBatchGeneratedStartBib(); + var sortKeys = bibGetGlobalBatchSortKeys(section); + + if (eprIds.length === 0) { + alert(bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone')); + return; + } + + if (startBib <= 0) { + alert(bibJs('jsGlobalGenNoStart') || bibJs('jsFieldsRequired')); + return; + } + + fetch('/ajax_bib_range.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: bibAppendGlobalBatchGeneratedParams( + 'action=batch_global_gen_create' + + '&eve_id=' + encodeURIComponent(moduleBib.dataset.eveId || ''), + section + ) + bibAjaxLangSuffix() + }) + .then(function (res) { + if (!res.ok) { + throw new Error(bibJs('jsErrGeneric')); + } + return res.json(); + }) + .then(function (createData) { + if (!createData || !createData.success || !createData.plans) { + throw new Error((createData && createData.message) || bibJs('jsErrGeneric')); + } + + var plans = createData.plans; + var globalTotal = (createData.stats && createData.stats.nb_participants) + ? createData.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('jsErrGeneric')); + } + + var pendingBefore = bibRowPendingBibs(row); + var offset = completedOffset; + + return bibRunBatchApplyChunks(row, plan.ranges, sortKeys, { + showResult: false, + manageLoader: false, + deferUiRefresh: true, + 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); + + 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') { + refreshBibAnomaliesPanel(); + } + + var totalAssigned = 0; + var totalExpected = 0; + + runResults.forEach(function (result) { + totalAssigned += result.assigned; + totalExpected += result.total; + }); + + var remaining = Math.max(0, totalExpected - totalAssigned); + + bibInvalidateGlobalBatchPanel(); + return bibEnsureGlobalBatchPanelLoaded().then(function () { + if (bibActiveTool === 'global_batch') { + bibInvalidateToolSlot('global_batch'); + bibMountToolContent('global_batch'); + initModuleBibTippy(document.getElementById('bib-tool-detail')); + } + + return Promise.all([ + updateGlobalBatchAnalysis(), + updateGlobalBatchGeneratedAnalysis() + ]).then(function () { + var genSection = bibGetGlobalBatchGeneratedSection(); + var infoEl = genSection + ? genSection.querySelector('.bib-global-gen-batch__info') + : null; + + if (totalAssigned === 0 && totalExpected > 0) { + var msgNone = bibJs('jsAssignNone') || bibJs('jsErrGeneric'); + if (infoEl) { + bibSetGlobalBatchInfo(infoEl, msgNone, 'error'); + } + alert(msgNone); + } else if (remaining > 0) { + var msgPartial = bibJsFmt('jsAssignPartial', totalAssigned, remaining); + if (infoEl) { + bibSetGlobalBatchInfo(infoEl, msgPartial, 'warning'); + } + alert(msgPartial); + } else if (totalAssigned > 0) { + var msgFull = bibJsFmt('jsAssignFull', totalAssigned); + if (infoEl) { + bibSetGlobalBatchInfo(infoEl, msgFull, 'ok'); + } + } + }); + }); + }); + }); + }) + .catch(function (err) { + alert(err.message || bibJs('jsErrGeneric')); + }) + .finally(function () { + bibBatchProgressForceHide(); + }); + } + function bibRunGlobalBatchGo() { - var bibGlobalBatchPanel = bibGetGlobalBatchPanel(); - if (!bibGlobalBatchPanel) { + var section = bibGetGlobalBatchExistingSection(); + if (!section) { return; } var plans = bibCollectGlobalBatchPlans(); - var sortKeys = bibGetGlobalBatchSortKeys(bibGlobalBatchPanel); + var sortKeys = bibGetGlobalBatchSortKeys(section); if (plans.length === 0) { alert(bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone')); @@ -903,7 +1200,7 @@ refreshBibAnomaliesPanel(); } - var panelRef = bibGetGlobalBatchPanel(); + var panelRef = bibGetGlobalBatchExistingSection(); var infoEl = panelRef ? panelRef.querySelector('.bib-global-batch__info') : null; var totalAssigned = 0; var totalExpected = 0; @@ -974,10 +1271,10 @@ if (e.target.closest('.btn-simuler-global-batch')) { e.preventDefault(); - var bibGlobalBatchPanel = bibGetGlobalBatchPanel(); + var existingSection = bibGetGlobalBatchExistingSection(); var plans = bibCollectGlobalBatchPlans(); - var sortKeys = bibGlobalBatchPanel - ? bibGetGlobalBatchSortKeys(bibGlobalBatchPanel) + var sortKeys = existingSection + ? bibGetGlobalBatchSortKeys(existingSection) : { sort1: '', sort2: '' }; if (plans.length === 0) { @@ -1016,14 +1313,69 @@ return; } + if (e.target.closest('.btn-simuler-global-gen-batch')) { + e.preventDefault(); + var genSection = bibGetGlobalBatchGeneratedSection(); + var eprIds = bibCollectGlobalBatchGeneratedEprIds(); + var startBib = bibGetGlobalBatchGeneratedStartBib(); + + if (eprIds.length === 0) { + alert(bibJs('jsGlobalNoEpr') || bibJs('jsBatchNone')); + return; + } + + if (startBib <= 0) { + alert(bibJs('jsGlobalGenNoStart') || bibJs('jsFieldsRequired')); + return; + } + + bibFetch('/ajax_bib_range.php', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: bibAppendGlobalBatchGeneratedParams( + 'action=batch_global_gen_simulate' + + '&eve_id=' + encodeURIComponent(moduleBib.dataset.eveId || ''), + genSection + ) + bibAjaxLangSuffix() + }) + .then(function (res) { return res.json(); }) + .then(function (data) { + if (!data.success) { + alert(data.message || bibJs('jsErrGeneric')); + return; + } + + var existing = document.querySelector('.epr-row-view'); + if (existing) { + existing.remove(); + } + + var anchor = bibGetGlobalBatchPanel(); + if (anchor) { + anchor.insertAdjacentHTML('afterend', data.html); + } + }); + + return; + } + if (e.target.closest('.btn-go-global-batch')) { e.preventDefault(); bibRunGlobalBatchGo(); + return; + } + + if (e.target.closest('.btn-go-global-gen-batch')) { + e.preventDefault(); + bibRunGlobalBatchGeneratedGo(); } }); 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')) { + 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' + )) { return; } @@ -1032,17 +1384,24 @@ return; } - var eprBlock = e.target.closest('.bib-global-epr'); - if (eprBlock && e.target.classList.contains('bib-global-epr-select')) { - var checked = e.target.checked; - eprBlock.querySelectorAll('.bib-global-range-select').forEach(function (cb) { - cb.disabled = !checked; - if (!checked) { - cb.checked = false; - } - }); + if (e.target.closest('.bib-global-batch--existing')) { + var eprBlock = e.target.closest('.bib-global-epr'); + if (eprBlock && e.target.classList.contains('bib-global-epr-select')) { + var checked = e.target.checked; + eprBlock.querySelectorAll('.bib-global-range-select').forEach(function (cb) { + cb.disabled = !checked; + if (!checked) { + cb.checked = false; + } + }); + } + updateGlobalBatchAnalysis(); + return; + } + + if (e.target.closest('.bib-global-batch--generated')) { + updateGlobalBatchGeneratedAnalysis(); } - updateGlobalBatchAnalysis(); }); function bibRowPendingBibs(row) { diff --git a/php/inc_fx_bib_global_generated.php b/php/inc_fx_bib_global_generated.php new file mode 100644 index 0000000..b2316bd --- /dev/null +++ b/php/inc_fx_bib_global_generated.php @@ -0,0 +1,436 @@ +fxGetVar(" + SELECT COUNT(*) + FROM inscriptions_epreuves_bib + WHERE epr_id = $epr_id + AND epr_bib_start IS NOT NULL + AND epr_bib_finish IS NOT NULL + "); + + return $intCount > 0; +} + +/** + * Épreuves solo sans aucune séquence, triées par epr_id (ordre page). + * + * @return array[] lignes inscriptions_epreuves + */ +function fxBibGetSoloEpreuvesWithoutSequence($int_eve_id, $tabEpreuves = null) { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + if ($int_eve_id <= 0) { + return []; + } + + if (!is_array($tabEpreuves)) { + $tabEpreuves = $objDatabase->fxGetResults( + "SELECT * FROM inscriptions_epreuves e" + . " WHERE e.eve_id = $int_eve_id AND epr_actif = 1" + . " ORDER BY epr_id" + ); + } + + $out = []; + + foreach ($tabEpreuves as $epr) { + if ((int)($epr['epr_equipe'] ?? 0) === 1) { + continue; + } + + if (fxBibEpreuveHasAnySequence((int)$epr['epr_id'])) { + continue; + } + + $out[] = $epr; + } + + return $out; +} + +/** Bloc 1 disponible : au moins 2 épreuves solo avec séquences sélectionnables. */ +function fxBibGlobalBatchHasExistingBlock($int_eve_id, $tabEpreuves) { + $int_eve_id = (int)$int_eve_id; + $intCount = 0; + + foreach ($tabEpreuves as $epr) { + if ((int)($epr['epr_equipe'] ?? 0) === 1) { + continue; + } + + if (empty(fxBibGetSelectableRangesForGlobalBatch((int)$epr['epr_id']))) { + continue; + } + + $intCount++; + if ($intCount >= 2) { + return true; + } + } + + return false; +} + +/** Bloc 2 disponible : au moins 1 épreuve solo sans séquence. */ +function fxBibGlobalBatchHasGeneratedBlock($int_eve_id, $tabEpreuves) { + return count(fxBibGetSoloEpreuvesWithoutSequence($int_eve_id, $tabEpreuves)) >= 1; +} + +/** Tuile / coquille assignation globale visible si l'un des deux blocs est utilisable. */ +function fxBibGlobalBatchToolIsAvailable($int_eve_id, $tabEpreuves) { + return fxBibGlobalBatchHasExistingBlock($int_eve_id, $tabEpreuves) + || fxBibGlobalBatchHasGeneratedBlock($int_eve_id, $tabEpreuves); +} + +/** + * Valide et ordonne les epr_id du bloc généré (solo, sans séquence, ordre epr_id). + * + * @return int[] + */ +function fxBibParseGlobalBatchGeneratedEprIds($eprIds, $int_eve_id) { + if (!is_array($eprIds)) { + return []; + } + + $int_eve_id = (int)$int_eve_id; + $tabWanted = []; + + foreach ($eprIds as $epr_id) { + $epr_id = (int)$epr_id; + if ($epr_id <= 0) { + continue; + } + + $check = fxBibAssertSoloEpreuveForGlobalBatch($epr_id, $int_eve_id); + if (!$check['success']) { + continue; + } + + if (fxBibEpreuveHasAnySequence($epr_id)) { + continue; + } + + $tabWanted[$epr_id] = true; + } + + if (empty($tabWanted)) { + return []; + } + + $tabIds = array_keys($tabWanted); + sort($tabIds, SORT_NUMERIC); + + return $tabIds; +} + +/** + * Plan de plages générées : taille = a_assigner (sans marge), enchaînement par epr_id. + * + * @return array[] [{epr_id, start, end, nb_pending, skip}] + */ +function fxBibBuildGeneratedRangePlan($int_eve_id, $tabEprIds, $intStartBib, $strSort1, $strSort2) { + $int_eve_id = (int)$int_eve_id; + $intStartBib = (int)$intStartBib; + $tabEprIds = fxBibParseGlobalBatchGeneratedEprIds($tabEprIds, $int_eve_id); + $intCursor = $intStartBib; + $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; + } + + $intStart = $intCursor; + $intEnd = $intCursor + $intPending - 1; + + $tabPlan[] = [ + 'epr_id' => $epr_id, + 'start' => $intStart, + 'end' => $intEnd, + 'nb_pending' => $intPending, + 'skip' => false, + ]; + + $intCursor = $intEnd + 1; + } + + return $tabPlan; +} + +/** Plages virtuelles pour simulation (même format que fxGetRangesForBatchAssign). */ +function fxBibBuildVirtualRangesForPlan($intStart, $intEnd) { + $intStart = (int)$intStart; + $intEnd = (int)$intEnd; + + if ($intStart <= 0 || $intEnd < $intStart) { + return []; + } + + return [[ + 'id' => 0, + 'start' => $intStart, + 'end' => $intEnd, + ]]; +} + +/** + * Valide le plan généré (chevauchements, épreuves sans séquence, au moins un à assigner). + */ +function fxBibValidateGeneratedRangePlan($int_eve_id, $tabPlan, $strLangue = 'fr') { + $int_eve_id = (int)$int_eve_id; + + if (!is_array($tabPlan) || empty($tabPlan)) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'), + ]; + } + + $blnHasAssignable = false; + + foreach ($tabPlan as $item) { + if (!empty($item['skip'])) { + continue; + } + + $blnHasAssignable = true; + $epr_id = (int)($item['epr_id'] ?? 0); + $intStart = (int)($item['start'] ?? 0); + $intEnd = (int)($item['end'] ?? 0); + + if ($epr_id <= 0 || $intStart <= 0 || $intEnd < $intStart) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'), + ]; + } + + if (fxBibEpreuveHasAnySequence($epr_id)) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_has_seq'), + ]; + } + + $tabOverlaps = fxBibGetOverlappingRanges($int_eve_id, $intStart, $intEnd, 0, $epr_id); + if (!empty($tabOverlaps)) { + return [ + 'success' => false, + 'message' => fxBibFormatOverlapConflictMessage($tabOverlaps, $strLangue), + ]; + } + } + + if (!$blnHasAssignable) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_no_pending'), + ]; + } + + return ['success' => true]; +} + +/** + * Crée les séquences en BD pour le plan validé. + * + * @return array{success:bool, message?:string, plans?:array} + */ +function fxBibCreateGeneratedRanges($int_eve_id, $tabPlan, $strLangue = 'fr') { + global $objDatabase; + + $int_eve_id = (int)$int_eve_id; + $tabCreated = []; + + foreach ($tabPlan as $item) { + if (!empty($item['skip'])) { + continue; + } + + $epr_id = (int)($item['epr_id'] ?? 0); + $intStart = (int)($item['start'] ?? 0); + $intEnd = (int)($item['end'] ?? 0); + + if ($epr_id <= 0 || $intStart <= 0 || $intEnd < $intStart) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'), + ]; + } + + if (fxBibEpreuveHasAnySequence($epr_id)) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_global_batch_gen_has_seq'), + ]; + } + + $tabOverlaps = fxBibGetOverlappingRanges($int_eve_id, $intStart, $intEnd, 0, $epr_id); + if (!empty($tabOverlaps)) { + return [ + 'success' => false, + 'message' => fxBibFormatOverlapConflictMessage($tabOverlaps, $strLangue), + ]; + } + + $objDatabase->fxQuery(" + INSERT INTO inscriptions_epreuves_bib + (epr_id, epr_bib_start, epr_bib_finish, update_date, update_qui, eve_id) + VALUES + ($epr_id, $intStart, $intEnd, NOW(), 'admin', $int_eve_id) + "); + + $intBibId = (int)($objDatabase->last_id ?? 0); + if ($intBibId <= 0) { + return [ + 'success' => false, + 'message' => fxBibMsg('bib_v4_ajax_error'), + ]; + } + + $tabCreated[] = [ + 'epr_id' => $epr_id, + 'ranges' => [$intBibId], + 'start' => $intStart, + 'end' => $intEnd, + ]; + } + + return [ + 'success' => true, + 'plans' => $tabCreated, + ]; +} + +/** Statistiques agrégées pour l'analyse du bloc généré. */ +function fxBibBuildGlobalBatchGeneratedAnalysisStats($tabPlan) { + $intEpreuves = 0; + $intPending = 0; + $intBibs = 0; + + foreach ($tabPlan as $item) { + if (!empty($item['skip'])) { + continue; + } + + $intEpreuves++; + $intPending += (int)($item['nb_pending'] ?? 0); + $intStart = (int)($item['start'] ?? 0); + $intEnd = (int)($item['end'] ?? 0); + if ($intEnd >= $intStart && $intStart > 0) { + $intBibs += ($intEnd - $intStart + 1); + } + } + + return [ + 'nb_epreuves' => $intEpreuves, + 'nb_participants' => $intPending, + 'nb_bibs' => $intBibs, + ]; +} + +/** + * Simulation globale bloc généré (plages virtuelles, aucune écriture BD). + * + * @return array{success:bool, html?:string, info?:string, message?:string} + */ +function fxBibSimulateGlobalBatchGenerated($int_eve_id, $tabPlan, $strSort1, $strSort2, $strLangue) { + $int_eve_id = (int)$int_eve_id; + $check = fxBibValidateGeneratedRangePlan($int_eve_id, $tabPlan, $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']; + $intStart = (int)$item['start']; + $intEnd = (int)$item['end']; + $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); + $tabRanges = fxBibBuildVirtualRangesForPlan($intStart, $intEnd); + $simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation'); + + $intNbPending = count($participants); + $intParticipants += $intNbPending; + $intBibs += max(0, $intEnd - $intStart + 1); + + $tabEpreuve = fxGetEpreuve($epr_id); + $strEprLabel = $tabEpreuve ? fxBibEpreuveDisplayName($tabEpreuve, $strLangue) : ('#' . $epr_id); + + $strRangeLabel = fxBibMsg( + 'bib_v4_global_batch_gen_range_preview', + $intStart, + $intEnd, + $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, $intEnd - $intStart + 1), + $intNbPending, + max(0, $intNbPending - count($simu)) + ) + ) + . '
'; + } + + $info = fxBibMsg( + 'bib_v4_ajax_global_gen_sim_summary', + fxBibBuildGlobalBatchGeneratedAnalysisStats($tabPlan)['nb_epreuves'], + $intBibs, + $intParticipants + ); + + $html = '
' + . '
' . fxBibEsc($info) . '
' + . implode('', $htmlParts) + . '
'; + + return [ + 'success' => true, + 'html' => $html, + 'info' => $info, + ]; +} diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index 3febeab..3533ad0 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -3322,6 +3322,18 @@ function fxBibStaticFallback($clef) { 'bib_v4_js_global_no_seq' => ['fr' => 'Cochez au moins une séquence pour les épreuves sélectionnées.', 'en' => 'Check at least one sequence for the selected races.'], 'bib_v4_global_batch_plan_invalid' => ['fr' => 'Sélection invalide : choisissez au moins une épreuve solo et des séquences.', 'en' => 'Invalid selection: pick at least one solo race and sequences.'], 'bib_v4_global_batch_need_two' => ['fr' => "L'assignation globale nécessite au moins deux épreuves solo avec des séquences disponibles.", 'en' => 'Global assignment requires at least two solo races with available sequences.'], + 'bib_v4_global_batch_existing_title' => ['fr' => 'Assignation des dossards avec séquences existantes', 'en' => 'Bib assignment with existing sequences'], + 'bib_v4_global_batch_generated_title' => ['fr' => 'Assignation de dossards sans séquence existante', 'en' => 'Bib assignment without existing sequences'], + 'bib_v4_global_batch_start_bib' => ['fr' => 'Dossard numéro 1', 'en' => 'First bib number'], + 'bib_v4_global_batch_gen_hint' => ['fr' => 'Épreuves solo sans séquence : une plage est créée pour chaque épreuve sélectionnée, à la taille exacte des inscriptions à assigner. Les numéros se suivent dans l\'ordre des épreuves.', 'en' => 'Solo races without sequences: one range is created per selected race, sized exactly to registrations to assign. Bib numbers continue in race order.'], + 'bib_v4_global_batch_gen_range_preview' => ['fr' => 'Plage prévue : %d → %d · %d à assigner', 'en' => 'Planned range: %d → %d · %d to assign'], + 'bib_v4_global_batch_gen_plan_invalid' => ['fr' => 'Sélection invalide : dossard de départ et au moins une épreuve avec inscriptions à assigner.', 'en' => 'Invalid selection: starting bib and at least one race with registrations to assign.'], + 'bib_v4_global_batch_gen_no_pending' => ['fr' => 'Aucune inscription à assigner dans les épreuves sélectionnées.', 'en' => 'No registrations to assign in the selected races.'], + 'bib_v4_global_batch_gen_has_seq' => ['fr' => 'Une épreuve sélectionnée a déjà une séquence — rechargez le panneau.', 'en' => 'A selected race already has a sequence — reload the panel.'], + 'bib_v4_global_batch_gen_no_start' => ['fr' => 'Indiquez le numéro du premier dossard.', 'en' => 'Enter the first bib number.'], + 'bib_v4_global_batch_unavailable' => ['fr' => 'Aucune assignation globale disponible pour cet événement.', 'en' => 'No global assignment available for this event.'], + 'bib_v4_ajax_global_gen_sim_summary' => ['fr' => '%d épreuve(s) · %d dossards · %d à assigner', 'en' => '%d race(s) · %d bibs · %d to assign'], + 'bib_v4_ajax_global_gen_analysis' => ['fr' => '%d épreuve(s) · plage %d → %d · %d à assigner', 'en' => '%d race(s) · range %d → %d · %d to assign'], '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'], @@ -3572,6 +3584,8 @@ function fxBibModuleJsDataAttrs() { 'batch-progress' => 'bib_v4_js_batch_progress', 'global-no-epr' => 'bib_v4_js_global_no_epr', 'global-no-seq' => 'bib_v4_js_global_no_seq', + 'global-gen-no-start' => 'bib_v4_global_batch_gen_no_start', + 'global-gen-no-epr' => 'bib_v4_js_global_no_epr', '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', @@ -4508,7 +4522,7 @@ function renderBibToolNav($int_eve_id, $tabEpreuves, $strLangue = 'fr') { - = 2) { + @@ -4529,20 +4543,14 @@ function renderBibToolNav($int_eve_id, $tabEpreuves, $strLangue = 'fr') { return ob_get_clean(); } +// MSIN-4443 — Assignation globale sans séquence (module dédié). +require_once __DIR__ . '/inc_fx_bib_global_generated.php'; + /** - * MSIN-4436 — Coquille assignation globale (aucun calcul séquence au chargement page). + * MSIN-4436 / MSIN-4443 — Coquille assignation globale (chargement différé à l'ouverture). */ function renderBibGlobalBatchPanelShell($int_eve_id, $tabEpreuves, $strLangue) { - $int_eve_id = (int)$int_eve_id; - $intSolo = 0; - - foreach ($tabEpreuves as $epr) { - if ((int)($epr['epr_equipe'] ?? 0) !== 1) { - $intSolo++; - } - } - - if ($intSolo < 2) { + if (!fxBibGlobalBatchToolIsAvailable($int_eve_id, $tabEpreuves)) { return ''; } @@ -4561,11 +4569,9 @@ function renderBibGlobalBatchPanelShell($int_eve_id, $tabEpreuves, $strLangue) { } /** - * MSIN-4436 — Panneau assignation globale (épreuves solo uniquement). - * Pré-coche toutes les séquences disponibles ; l'utilisateur peut en retirer avant GO. - * Appelé en AJAX à l'ouverture du panneau (pas au chargement page). + * MSIN-4436 — Bloc assignation avec séquences existantes. */ -function renderBibGlobalBatchPanel($int_eve_id, $tabEpreuves, $tabBibAssignments, $strLangue) { +function renderBibGlobalBatchPanelExisting($int_eve_id, $tabEpreuves, $strLangue) { $int_eve_id = (int)$int_eve_id; $tabSolo = []; @@ -4585,29 +4591,16 @@ function renderBibGlobalBatchPanel($int_eve_id, $tabEpreuves, $tabBibAssignments ]; } - if (count($tabSolo) < 2) { - if (empty($tabSolo)) { - return ''; - } + ob_start(); + ?> +
+

- ob_start(); - ?> -
+

-
- -
-
+

@@ -4687,6 +4680,136 @@ function renderBibGlobalBatchPanel($int_eve_id, $tabEpreuves, $tabBibAssignments
+ +
+ +
+

+

+

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

+ +
+ + + + + + + + + + + + +
+
+ +
+
+ '; + } + echo $strGenerated; + } + ?>