491 lines
14 KiB
PHP
491 lines
14 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 actives (avec ou sans séquence), triées par epr_id.
|
|
* MSIN-4445 — utilisé par l'assignation libre.
|
|
*
|
|
* @return array[] lignes inscriptions_epreuves
|
|
*/
|
|
function fxBibGetSoloEpreuves($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;
|
|
}
|
|
|
|
$out[] = $epr;
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* É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) {
|
|
$out = [];
|
|
|
|
foreach (fxBibGetSoloEpreuves($int_eve_id, $tabEpreuves) as $epr) {
|
|
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;
|
|
}
|
|
|
|
/** MSIN-4445 — Bloc libre : au moins 1 épreuve solo (avec ou sans séquence). */
|
|
function fxBibGlobalBatchHasFreeBlock($int_eve_id, $tabEpreuves) {
|
|
return count(fxBibGetSoloEpreuves($int_eve_id, $tabEpreuves)) >= 1;
|
|
}
|
|
|
|
/** Tuile / coquille assignation globale visible si l'un des blocs est utilisable. */
|
|
function fxBibGlobalBatchToolIsAvailable($int_eve_id, $tabEpreuves) {
|
|
return fxBibGlobalBatchHasExistingBlock($int_eve_id, $tabEpreuves)
|
|
|| fxBibGlobalBatchHasGeneratedBlock($int_eve_id, $tabEpreuves)
|
|
|| fxBibGlobalBatchHasFreeBlock($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 = [];
|
|
|
|
if (fxBibProductionEventIsLocked($int_eve_id)) {
|
|
return [
|
|
'success' => false,
|
|
'message' => fxBibMsg('bib_v5_production_locked_error'),
|
|
];
|
|
}
|
|
|
|
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),
|
|
];
|
|
}
|
|
|
|
// MSIN-4443 — Création séquence : vérifier INSERT + last_id (évite le message générique « Erreur »).
|
|
$sqlInsert = "
|
|
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)
|
|
";
|
|
$qryInsert = $objDatabase->fxQuery($sqlInsert);
|
|
if ($qryInsert === false) {
|
|
return [
|
|
'success' => false,
|
|
'message' => fxBibMsg('bib_v4_global_batch_gen_insert_failed'),
|
|
];
|
|
}
|
|
|
|
$intBibId = 0;
|
|
if (method_exists($objDatabase, 'fxGetLastId')) {
|
|
$intBibId = (int)$objDatabase->fxGetLastId();
|
|
} elseif (isset($objDatabase->last_id)) {
|
|
$intBibId = (int)$objDatabase->last_id;
|
|
}
|
|
|
|
// Repli : si insert_id absent, retrouver la ligne fraîchement créée.
|
|
if ($intBibId <= 0) {
|
|
$intBibId = (int)$objDatabase->fxGetVar("
|
|
SELECT epr_bib_id
|
|
FROM inscriptions_epreuves_bib
|
|
WHERE epr_id = $epr_id
|
|
AND epr_bib_start = $intStart
|
|
AND epr_bib_finish = $intEnd
|
|
AND eve_id = $int_eve_id
|
|
ORDER BY epr_bib_id DESC
|
|
LIMIT 1
|
|
");
|
|
}
|
|
|
|
if ($intBibId <= 0) {
|
|
return [
|
|
'success' => false,
|
|
'message' => fxBibMsg('bib_v4_global_batch_gen_insert_failed'),
|
|
];
|
|
}
|
|
|
|
$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 = fxBibWrapGlobalSimView($info, implode('', $htmlParts), 'bib-global-sim-view--generated');
|
|
|
|
return [
|
|
'success' => true,
|
|
'html' => $html,
|
|
'info' => $info,
|
|
];
|
|
}
|