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();
- ?>
-
-
-