Files
ms1inscription-v5/php/inc_fx_bib_global_generated.php
stephan 91ee36c121 Add batch global analysis, simulation, and creation functionalities; update styles and localization, increment version code to 4.72.780
This commit introduces new actions for batch global analysis, simulation, and creation without existing sequences. The JavaScript and PHP files are updated to handle these new functionalities, including validation and response handling. CSS styles are enhanced for better layout and user experience. Localization keys are added for improved clarity in user messages. The version code is incremented to 4.72.780 to reflect these changes.
2026-07-09 12:15:04 -04:00

437 lines
12 KiB
PHP

<?php
/**
* MSIN-4443 — Assignation globale solo sans séquence existante.
* Création automatique de plages de dossards puis assignation via le moteur batch existant.
*/
/** True si l'épreuve a au moins une séquence enregistrée. */
function fxBibEpreuveHasAnySequence($epr_id) {
global $objDatabase;
$epr_id = (int)$epr_id;
if ($epr_id <= 0) {
return false;
}
$intCount = (int)$objDatabase->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[] = '<div class="bib-global-sim-section">'
. '<h4 class="bib-global-sim-section__title">' . fxBibEsc($strEprLabel) . '</h4>'
. '<p class="bib-global-sim-section__range text-muted small">' . fxBibEsc($strRangeLabel) . '</p>'
. 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))
)
)
. '</div>';
}
$info = fxBibMsg(
'bib_v4_ajax_global_gen_sim_summary',
fxBibBuildGlobalBatchGeneratedAnalysisStats($tabPlan)['nb_epreuves'],
$intBibs,
$intParticipants
);
$html = '<div class="epr-row-view bib-global-sim-view bib-global-sim-view--generated">'
. '<div class="bib-global-sim-view__summary">' . fxBibEsc($info) . '</div>'
. implode('', $htmlParts)
. '</div>';
return [
'success' => true,
'html' => $html,
'info' => $info,
];
}