445 lines
13 KiB
PHP
445 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* MSIN-4445 — Assignation globale solo libre (sans création de séquence).
|
|
* Plage Début/Fin (ou Début/Qté) fournie par l'utilisateur ; assignation via plages virtuelles.
|
|
*/
|
|
|
|
/**
|
|
* Calcule le feedback quantité vs inscriptions à assigner.
|
|
*
|
|
* @return array{status:string, amount:int, qty:int, pending:int}
|
|
*/
|
|
function fxBibFreeQtyFeedback($intQty, $intPending) {
|
|
$intQty = (int)$intQty;
|
|
$intPending = (int)$intPending;
|
|
$intDiff = $intQty - $intPending;
|
|
|
|
if ($intDiff === 0) {
|
|
return [
|
|
'status' => 'juste',
|
|
'amount' => 0,
|
|
'qty' => $intQty,
|
|
'pending' => $intPending,
|
|
];
|
|
}
|
|
|
|
if ($intDiff > 0) {
|
|
return [
|
|
'status' => 'supplementaire',
|
|
'amount' => $intDiff,
|
|
'qty' => $intQty,
|
|
'pending' => $intPending,
|
|
];
|
|
}
|
|
|
|
return [
|
|
'status' => 'manquante',
|
|
'amount' => abs($intDiff),
|
|
'qty' => $intQty,
|
|
'pending' => $intPending,
|
|
];
|
|
}
|
|
|
|
/** Libellé Info pour le feedback quantité libre. */
|
|
function fxBibFreeQtyFeedbackMessage(array $tabFeedback) {
|
|
$strStatus = $tabFeedback['status'] ?? '';
|
|
$intAmount = (int)($tabFeedback['amount'] ?? 0);
|
|
|
|
if ($strStatus === 'juste') {
|
|
return fxBibMsg('bib_v4_global_batch_free_qty_juste');
|
|
}
|
|
|
|
if ($strStatus === 'supplementaire') {
|
|
return fxBibMsg('bib_v4_global_batch_free_qty_supplementaire', $intAmount);
|
|
}
|
|
|
|
if ($strStatus === 'manquante') {
|
|
return fxBibMsg('bib_v4_global_batch_free_qty_manquante', $intAmount);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* MSIN-4445 — Nombre de dossards déjà assignés dans [start, end] pour l'événement.
|
|
* Jointure via épreuve : plus fiable que p.eve_id seul.
|
|
*/
|
|
function fxBibCountAssignedBibsInRange($int_eve_id, $intStart, $intEnd) {
|
|
global $objDatabase;
|
|
|
|
$int_eve_id = (int)$int_eve_id;
|
|
$intStart = (int)$intStart;
|
|
$intEnd = (int)$intEnd;
|
|
|
|
if ($int_eve_id <= 0 || $intStart <= 0 || $intEnd < $intStart) {
|
|
return 0;
|
|
}
|
|
|
|
$sqlBib = fxBibSqlNoBibUnsigned('p.no_bib');
|
|
|
|
return (int)$objDatabase->fxGetVar("
|
|
SELECT COUNT(DISTINCT $sqlBib)
|
|
FROM resultats_participants p
|
|
INNER JOIN inscriptions_epreuves e ON e.epr_id = p.epr_id
|
|
WHERE e.eve_id = $int_eve_id
|
|
AND IFNULL(p.is_cancelled, 0) = 0
|
|
AND p.no_bib IS NOT NULL
|
|
AND TRIM(p.no_bib) <> ''
|
|
AND $sqlBib BETWEEN $intStart AND $intEnd
|
|
");
|
|
}
|
|
|
|
/**
|
|
* MSIN-4445 — Risque de doublons pour une plage libre.
|
|
* Avertit si des dossards de la plage sont déjà portés, ou si une séquence
|
|
* existante chevauche la plage (même hors épreuves sélectionnées).
|
|
*
|
|
* @return array{has:bool, count:int, message:string}
|
|
*/
|
|
function fxBibFreeDuplicateRisk($int_eve_id, $tabPlan, $intStart = 0, $intEnd = 0) {
|
|
$int_eve_id = (int)$int_eve_id;
|
|
$intStart = (int)$intStart;
|
|
$intEnd = (int)$intEnd;
|
|
|
|
if ($intStart <= 0 || $intEnd < $intStart) {
|
|
// Repli : bornes du plan si Début/Fin absents.
|
|
foreach ((array)$tabPlan as $item) {
|
|
if (!empty($item['skip'])) {
|
|
continue;
|
|
}
|
|
$intItemStart = (int)($item['start'] ?? 0);
|
|
$intItemEnd = (int)($item['end'] ?? 0);
|
|
if ($intItemStart <= 0 || $intItemEnd < $intItemStart) {
|
|
continue;
|
|
}
|
|
if ($intStart <= 0 || $intItemStart < $intStart) {
|
|
$intStart = $intItemStart;
|
|
}
|
|
if ($intItemEnd > $intEnd) {
|
|
$intEnd = $intItemEnd;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($int_eve_id <= 0 || $intStart <= 0 || $intEnd < $intStart) {
|
|
return ['has' => false, 'count' => 0, 'message' => ''];
|
|
}
|
|
|
|
$intCount = fxBibCountAssignedBibsInRange($int_eve_id, $intStart, $intEnd);
|
|
|
|
// Séquences déjà configurées qui chevauchent la plage (autre épreuve).
|
|
$tabOverlaps = fxBibGetOverlappingRanges($int_eve_id, $intStart, $intEnd, 0, 0);
|
|
$intOverlapSeq = is_array($tabOverlaps) ? count($tabOverlaps) : 0;
|
|
|
|
if ($intCount <= 0 && $intOverlapSeq <= 0) {
|
|
return ['has' => false, 'count' => 0, 'message' => ''];
|
|
}
|
|
|
|
return [
|
|
'has' => true,
|
|
'count' => max($intCount, $intOverlapSeq),
|
|
'message' => fxBibMsg('bib_v4_global_batch_free_dupes_warn'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* MSIN-4445 — Valide et ordonne les epr_id du bloc libre (solo, avec ou sans séquence).
|
|
*
|
|
* @return int[]
|
|
*/
|
|
function fxBibParseGlobalBatchFreeEprIds($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;
|
|
}
|
|
|
|
$tabWanted[$epr_id] = true;
|
|
}
|
|
|
|
if (empty($tabWanted)) {
|
|
return [];
|
|
}
|
|
|
|
$tabIds = array_keys($tabWanted);
|
|
sort($tabIds, SORT_NUMERIC);
|
|
|
|
return $tabIds;
|
|
}
|
|
|
|
/**
|
|
* Plan de plages virtuelles bornées par Début/Fin (sans écriture BD).
|
|
* Enchaîne les épreuves comme le bloc généré, mais s'arrête à $intEnd.
|
|
*
|
|
* @return array[] [{epr_id, start, end, nb_pending, skip}]
|
|
*/
|
|
function fxBibBuildFreeRangePlan($int_eve_id, $tabEprIds, $intStart, $intEnd, $strSort1, $strSort2) {
|
|
$int_eve_id = (int)$int_eve_id;
|
|
$intStart = (int)$intStart;
|
|
$intEnd = (int)$intEnd;
|
|
$tabEprIds = fxBibParseGlobalBatchFreeEprIds($tabEprIds, $int_eve_id);
|
|
$intCursor = $intStart;
|
|
$tabPlan = [];
|
|
|
|
foreach ($tabEprIds as $epr_id) {
|
|
$participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2);
|
|
$intPending = count($participants);
|
|
|
|
if ($intPending <= 0) {
|
|
$tabPlan[] = [
|
|
'epr_id' => $epr_id,
|
|
'start' => 0,
|
|
'end' => 0,
|
|
'nb_pending' => 0,
|
|
'skip' => true,
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$intRemaining = $intEnd - $intCursor + 1;
|
|
if ($intRemaining <= 0) {
|
|
$tabPlan[] = [
|
|
'epr_id' => $epr_id,
|
|
'start' => 0,
|
|
'end' => 0,
|
|
'nb_pending' => $intPending,
|
|
'skip' => true,
|
|
];
|
|
continue;
|
|
}
|
|
|
|
$intTake = min($intPending, $intRemaining);
|
|
$intRangeStart = $intCursor;
|
|
$intRangeEnd = $intCursor + $intTake - 1;
|
|
|
|
$tabPlan[] = [
|
|
'epr_id' => $epr_id,
|
|
'start' => $intRangeStart,
|
|
'end' => $intRangeEnd,
|
|
'nb_pending' => $intPending,
|
|
'skip' => false,
|
|
];
|
|
|
|
$intCursor = $intRangeEnd + 1;
|
|
}
|
|
|
|
return $tabPlan;
|
|
}
|
|
|
|
/**
|
|
* Valide le plan libre (plage valide + au moins une assignation possible).
|
|
* Pas de contrôle de chevauchement ni d'insertion de séquence (MSIN-4445).
|
|
*/
|
|
function fxBibValidateFreeRangePlan($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue = 'fr') {
|
|
$int_eve_id = (int)$int_eve_id;
|
|
$intStart = (int)$intStart;
|
|
$intEnd = (int)$intEnd;
|
|
|
|
if ($intStart <= 0 || $intEnd < $intStart) {
|
|
return [
|
|
'success' => false,
|
|
'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'),
|
|
];
|
|
}
|
|
|
|
if (!is_array($tabPlan) || empty($tabPlan)) {
|
|
return [
|
|
'success' => false,
|
|
'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'),
|
|
];
|
|
}
|
|
|
|
$blnHasAssignable = false;
|
|
|
|
foreach ($tabPlan as $item) {
|
|
if (!empty($item['skip'])) {
|
|
continue;
|
|
}
|
|
|
|
$epr_id = (int)($item['epr_id'] ?? 0);
|
|
$intItemStart = (int)($item['start'] ?? 0);
|
|
$intItemEnd = (int)($item['end'] ?? 0);
|
|
|
|
if ($epr_id <= 0 || $intItemStart <= 0 || $intItemEnd < $intItemStart) {
|
|
return [
|
|
'success' => false,
|
|
'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'),
|
|
];
|
|
}
|
|
|
|
// MSIN-4445 — OK même si une séquence existe déjà (aucune création de séquence).
|
|
$blnHasAssignable = true;
|
|
}
|
|
|
|
if (!$blnHasAssignable) {
|
|
return [
|
|
'success' => false,
|
|
'message' => fxBibMsg('bib_v4_global_batch_gen_no_pending'),
|
|
];
|
|
}
|
|
|
|
return ['success' => true];
|
|
}
|
|
|
|
/** Stats agrégées + feedback quantité pour l'analyse libre. */
|
|
function fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd) {
|
|
$int_eve_id = (int)$int_eve_id;
|
|
$intStart = (int)$intStart;
|
|
$intEnd = (int)$intEnd;
|
|
$intQty = ($intStart > 0 && $intEnd >= $intStart) ? ($intEnd - $intStart + 1) : 0;
|
|
$tabStats = fxBibBuildGlobalBatchGeneratedAnalysisStats($tabPlan);
|
|
|
|
// MSIN-4445 — Feedback vs total à assigner (y compris épreuves non couvertes par la plage).
|
|
$intPendingAll = 0;
|
|
foreach ($tabPlan as $item) {
|
|
$intPendingAll += (int)($item['nb_pending'] ?? 0);
|
|
}
|
|
|
|
$tabFeedback = fxBibFreeQtyFeedback($intQty, $intPendingAll);
|
|
$tabDupes = fxBibFreeDuplicateRisk($int_eve_id, $tabPlan, $intStart, $intEnd);
|
|
|
|
return [
|
|
'stats' => $tabStats,
|
|
'feedback' => $tabFeedback,
|
|
'duplicates' => $tabDupes,
|
|
'info' => fxBibMsg(
|
|
'bib_v4_ajax_global_free_analysis',
|
|
$tabStats['nb_epreuves'],
|
|
$intStart,
|
|
$intEnd,
|
|
$intPendingAll,
|
|
fxBibFreeQtyFeedbackMessage($tabFeedback)
|
|
),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Simulation globale libre (plages virtuelles, aucune écriture BD).
|
|
*
|
|
* @return array{success:bool, html?:string, info?:string, message?:string}
|
|
*/
|
|
function fxBibSimulateGlobalBatchFree($int_eve_id, $tabPlan, $intStart, $intEnd, $strSort1, $strSort2, $strLangue) {
|
|
$check = fxBibValidateFreeRangePlan($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue);
|
|
|
|
if (!$check['success']) {
|
|
return $check;
|
|
}
|
|
|
|
$htmlParts = [];
|
|
$intParticipants = 0;
|
|
$intBibs = 0;
|
|
|
|
foreach ($tabPlan as $item) {
|
|
if (!empty($item['skip'])) {
|
|
continue;
|
|
}
|
|
|
|
$epr_id = (int)$item['epr_id'];
|
|
$intItemStart = (int)$item['start'];
|
|
$intItemEnd = (int)$item['end'];
|
|
$participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2);
|
|
$tabRanges = fxBibBuildVirtualRangesForPlan($intItemStart, $intItemEnd);
|
|
$simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation');
|
|
|
|
$intNbPending = count($participants);
|
|
$intParticipants += $intNbPending;
|
|
$intBibs += max(0, $intItemEnd - $intItemStart + 1);
|
|
|
|
$tabEpreuve = fxGetEpreuve($epr_id);
|
|
$strEprLabel = $tabEpreuve ? fxBibEpreuveDisplayName($tabEpreuve, $strLangue) : ('#' . $epr_id);
|
|
|
|
$strRangeLabel = fxBibMsg(
|
|
'bib_v4_global_batch_gen_range_preview',
|
|
$intItemStart,
|
|
$intItemEnd,
|
|
$intNbPending
|
|
);
|
|
|
|
$htmlParts[] = '<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, $intItemEnd - $intItemStart + 1),
|
|
$intNbPending,
|
|
max(0, $intNbPending - count($simu))
|
|
)
|
|
)
|
|
. '</div>';
|
|
}
|
|
|
|
$tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd);
|
|
$info = fxBibMsg(
|
|
'bib_v4_ajax_global_free_sim_summary',
|
|
$tabAnalysis['stats']['nb_epreuves'],
|
|
$intBibs,
|
|
(int)($tabAnalysis['feedback']['pending'] ?? $intParticipants),
|
|
fxBibFreeQtyFeedbackMessage($tabAnalysis['feedback'])
|
|
);
|
|
|
|
$html = fxBibWrapGlobalSimView($info, implode('', $htmlParts), 'bib-global-sim-view--free');
|
|
|
|
return [
|
|
'success' => true,
|
|
'html' => $html,
|
|
'info' => $info,
|
|
'feedback' => $tabAnalysis['feedback'],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Prépare les plans GO libres (sans INSERT séquence).
|
|
*
|
|
* @return array{success:bool, plans?:array, stats?:array, feedback?:array, message?:string}
|
|
*/
|
|
function fxBibPrepareFreeRangePlans($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue = 'fr') {
|
|
$check = fxBibValidateFreeRangePlan($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue);
|
|
|
|
if (!$check['success']) {
|
|
return $check;
|
|
}
|
|
|
|
$tabPlans = [];
|
|
|
|
foreach ($tabPlan as $item) {
|
|
if (!empty($item['skip'])) {
|
|
continue;
|
|
}
|
|
|
|
$tabPlans[] = [
|
|
'epr_id' => (int)$item['epr_id'],
|
|
'start' => (int)$item['start'],
|
|
'end' => (int)$item['end'],
|
|
];
|
|
}
|
|
|
|
$tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd);
|
|
|
|
return [
|
|
'success' => true,
|
|
'plans' => $tabPlans,
|
|
'stats' => $tabAnalysis['stats'],
|
|
'feedback' => $tabAnalysis['feedback'],
|
|
'duplicates' => $tabAnalysis['duplicates'],
|
|
];
|
|
}
|