This commit is contained in:
2026-05-27 10:48:18 -04:00
4 changed files with 367 additions and 173 deletions

25
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,25 @@
{
"sqltools.connections": [
{
"mysqlOptions": {
"authProtocol": "default",
"enableSsl": "Disabled"
},
"ssh": "Enabled",
"previewLimit": 50,
"server": "localhost",
"port": 3306,
"sshOptions": {
"port": 7822,
"host": "104.254.182.111",
"username": "devinscription",
"password": "M-G7+[NdA34H"
},
"driver": "MariaDB",
"name": "astral(dev) ms1inscription",
"username": "devinscription_bd",
"password": "x$@_v0-l_I((",
"database": "devinscription_preprod"
}
]
}

View File

@ -394,7 +394,7 @@ if ($action == 'batch_go') {
$nextBib = fxGetNextAvailableBib($epr_id, $tabRanges);
$simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation');
$summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants);
$summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id);
$info = "{$summary['nb_dispo']} disponibles / {$summary['nb_participants']} à assigner → manque {$summary['nb_manque']}";
// =====================
@ -451,7 +451,7 @@ if ($action == 'batch_apply') {
return in_array((int)$r['epr_bib_id'], $ids);
});
$summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants);
$summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id);
$assigned = count($exec);
$total = count($participants);

View File

@ -2039,7 +2039,12 @@ if ($strLangue == 'fr') {
if (totalEl) totalEl.textContent = data.info.total;
if (avecEl) avecEl.textContent = data.info.avec_bib;
syncBibUiState(row);
let sansBib = parseInt(data.info.sans_bib || 0, 10);
let sansBib = parseInt(
(data.info.a_assigner !== undefined && data.info.a_assigner !== null)
? data.info.a_assigner
: (data.info.sans_bib || 0),
10
);
if (isNaN(sansBib)) sansBib = 0;
let isResetMode = row.dataset.mode === 'reset';
let message = '';
@ -2332,6 +2337,23 @@ if ($strLangue == 'fr') {
let container = btnView.closest('.epr-block');
// Vérifier si une vue est déjà ouverte
let existing = document.querySelector('.epr-row-view');
// Toggle : si c'est le même œil qui a ouvert la vue, on la ferme et on s'arrête
if (existing
&& existing.dataset.eprId === epr_id
&& existing.dataset.rangeId === range_id
) {
existing.remove();
return;
}
// Un autre œil a été cliqué : fermer la vue précédente avant d'ouvrir la nouvelle
if (existing) {
existing.remove();
}
fetch('/ajax_bib_range.php', {
method: 'POST',
headers: {
@ -2360,14 +2382,17 @@ if ($strLangue == 'fr') {
let row = btnView.closest('.epr-row');
// 1. Supprimer toute vue existante
let existing = document.querySelector('.epr-row-view');
if (existing) {
existing.remove();
}
// 2. Injecter sous la bonne ligne
// Injecter la nouvelle vue sous la ligne du bouton cliqué,
// en mémorisant epr_id et range_id pour le toggle suivant
row.insertAdjacentHTML('afterend', data.html);
// Stocker les identifiants sur la vue injectée pour pouvoir détecter
// le 2e clic sur le même œil
let newView = row.nextElementSibling;
if (newView && newView.classList.contains('epr-row-view')) {
newView.dataset.eprId = epr_id;
newView.dataset.rangeId = range_id;
}
});
return;

View File

@ -2550,8 +2550,8 @@ function fxUpdateQuantites($intEpreuve, $intQuantite, $strLangue) {
}
// v2
function renderBatchConfig($tabBibAssignments, $strLangue = 'fr') {
// v2 — options d'ordre batch (table bib_assignements)
function renderBatchConfig($tabBibAssignments, $strLangue = 'fr', $isTeamEpreuve = false) {
ob_start();
?>
@ -2562,7 +2562,13 @@ function renderBatchConfig($tabBibAssignments, $strLangue = 'fr') {
<div class="mb-2">
<label>Ordre dassignation</label>
<select class="form-control form-control-sm batch-order">
<?php for ($j = 1; $j < count($tabBibAssignments)+1; $j++) { ?>
<?php for ($j = 1; $j < count($tabBibAssignments)+1; $j++) {
// bib_assignements.ba_equipe = 1 : option équipe seulement (ex. ba_id 5 « Nom d'équipe »)
// → masquée sur épreuve solo ; visible sur épreuve équipe (individuel ou 1 équipe = 1 dossard)
if ((int)($tabBibAssignments[$j]['ba_equipe'] ?? 0) === 1 && !$isTeamEpreuve) {
continue;
}
?>
<option value="<?php echo $tabBibAssignments[$j]['ba_id']; ?>">
<?php echo $tabBibAssignments[$j]['ba_nom_' . $strLangue]; ?>
</option>
@ -2784,7 +2790,10 @@ if ($teamMode <= 0) {
<div class="batch-config-container" style="display:none;">
<?php echo renderBatchConfig($tabBibAssignments, $strLangue); ?>
<?php
// 3e arg. : epr_equipe = 1 → afficher aussi les tris « équipe » (ba_equipe = 1 dans bib_assignements)
echo renderBatchConfig($tabBibAssignments, $strLangue, (int)$tabEpreuves[$i]['epr_equipe'] === 1);
?>
<button type="button" class="btn btn-sm btn-secondary btn-simuler-batch" style="display:none;">
Simuler
@ -2958,11 +2967,14 @@ function fxInfosBibEpreuve($epr_id){
$row = $objDatabase->fxGetRow($sql);
return [
$info = [
'total' => (int)($row['total'] ?? 0),
'avec_bib' => (int)($row['avec_bib'] ?? 0),
'sans_bib' => (int)($row['sans_bib'] ?? 0)
'sans_bib' => (int)($row['sans_bib'] ?? 0),
'a_assigner' => fxCountBatchAssignUnits($epr_id)
];
return $info;
}
function renderBibRanges($tabBibRanges, $epr_id = 0, $mode = 'assign') {
//print_rsl($tabBibRanges);
@ -3338,7 +3350,8 @@ function fxGetParticipantsForBatchAssign($epr_id, $ba_id) {
// =====================
// RÉCUPÉRER L'ORDER BY DYNAMIQUE
// Source : table bib_assignements
// Source : bib_assignements.ba_sql_order (choix du promoteur dans le batch)
// Ex. ba_id 5 : TRIM(c.pec_nom_equipe), p.par_nom, p.par_prenom
// =====================
$sqlOrder = "
SELECT ba_sql_order
@ -3359,11 +3372,10 @@ function fxGetParticipantsForBatchAssign($epr_id, $ba_id) {
}
// =====================
// PARTICIPANTS SOLO À ASSIGNER
// Important :
// PARTICIPANTS À ASSIGNER (solo et équipe)
// - jointure c.* requise pour les tris par nom d'équipe (pec_nom_equipe)
// - on ne prend que ceux sans no_bib
// - on exclut rol_id 3 et 4 comme dans l'ancien code
// - aucune écriture DB ici
// =====================
$sql = "
SELECT
@ -3497,201 +3509,333 @@ function fxGetNextAvailableBib($epr_id, $tabRanges) {
// 👉 aucun BIB disponible
return null;
}
function fxProcessBatchAssignments($epr_id, $participants, $tabRanges, $mode = 'simulation') {
function fxLoadUsedBibsForBatch($epr_id) {
global $objDatabase;
$assignments = [];
$teamMap = [];
$usedBib = [];
$sql = "
SELECT no_bib
FROM resultats_participants
WHERE epr_id = " . intval($epr_id) . "
AND no_bib IS NOT NULL
AND no_bib != ''
AND no_bib > 0
";
$rows = $objDatabase->fxGetResults($sql);
$updatedTeams = [];
$teamBibMap = [];
$nextTeamNumber = 1;
if (empty($participants) || empty($tabRanges)) {
return $assignments;
foreach ($rows as $r) {
$usedBib[(int)$r['no_bib']] = true;
}
return $usedBib;
}
/**
* Cherche le prochain dossard libre dans les séquences sélectionnées.
*
* Règle anti-doublon (mode execute / GO) :
* - solo ou équipe « dossards individuels » : un numéro = une personne (COUNT participants)
* - équipe « 1 équipe = 1 dossard » : un numéro = une équipe (COUNT DISTINCT pec_id)
* → plusieurs coéquipiers peuvent partager le même no_bib
*
* @param int $teamMode epr_bib_team_mode (1 = individuel, 3 = 1 équipe = 1 dossard)
*/
function fxFindNextBatchBib($epr_id, $tabRanges, $mode, &$usedBib, $teamMode = 1) {
global $objDatabase;
$epr_id = intval($epr_id);
$teamMode = (int)$teamMode;
// =====================
// MODE SIMULATION → cache en mémoire
// MODE EXECUTE → on ne fait PAS confiance au cache
// =====================
$usedBib = [];
foreach ($tabRanges as $range) {
$start = (int)$range['start'];
$end = (int)$range['end'];
if ($mode === 'simulation') {
// Charger les BIB déjà utilisés (1 seule fois)
$sql = "
SELECT no_bib
FROM resultats_participants
WHERE epr_id = $epr_id
AND no_bib IS NOT NULL
AND no_bib != ''
";
$rows = $objDatabase->fxGetResults($sql);
foreach ($rows as $r) {
$usedBib[(int)$r['no_bib']] = true;
for ($i = $start; $i <= $end; $i++) {
if ($mode === 'simulation') {
// Simulation : cache mémoire (même logique solo/équipe via $teamBibMap en amont)
if (!isset($usedBib[$i])) {
$usedBib[$i] = true;
return $i;
}
} elseif ($teamMode === 3) {
// GO — mode « 1 équipe = 1 dossard »
// Le numéro est libre s'il n'est pas déjà pris par une autre équipe.
$sqlCheck = "
SELECT COUNT(DISTINCT pec_id)
FROM resultats_participants
WHERE epr_id = $epr_id
AND no_bib = $i
AND is_cancelled = 0
AND pec_id > 0
";
if ((int)$objDatabase->fxGetVar($sqlCheck) === 0) {
return $i;
}
} else {
// GO — solo ou dossards individuels : un numéro ne peut servir qu'à une personne
$sqlCheck = "
SELECT COUNT(*)
FROM resultats_participants
WHERE epr_id = $epr_id
AND no_bib = $i
AND is_cancelled = 0
";
if ((int)$objDatabase->fxGetVar($sqlCheck) === 0) {
return $i;
}
}
}
}
// =====================
// BOUCLE PRINCIPALE
// =====================
foreach ($participants as $p) {
$par_id = (int)$p['par_id'];
$teamKey = (int)($p['pec_id_original'] ?? 0);
$teamMode = (int)($p['epr_bib_team_mode'] ?? 1);
$teamNumber = 0;
$teamMode = (int)($p['epr_bib_team_mode'] ?? 1);
if ($teamMode !== 3 && $teamKey > 0) {
if (!isset($teamMap[$teamKey])) {
$teamMap[$teamKey] = $nextTeamNumber;
$nextTeamNumber++;
}
$teamNumber = $teamMap[$teamKey];
}
$foundBib = null;
if ($teamMode === 3 && $teamKey > 0) {
if (isset($teamBibMap[$teamKey])) {
$foundBib = $teamBibMap[$teamKey];
$teamNumber = $teamBibMap[$teamKey];
}
return null;
}
/**
* Écrit le no_bib d'un participant (batch GO).
* Note : en mode « 1 équipe = 1 dossard », plusieurs lignes peuvent légitimement
* recevoir le même no_bib — ce n'est pas un doublon erroné.
*/
function fxApplyBatchParticipantBib($epr_id, $par_id, $foundBib) {
global $objDatabase;
// =====================
// CHERCHER BIB LIBRE
// =====================
if ($foundBib === null) {
$sqlUpdate = "
UPDATE resultats_participants
SET no_bib = " . intval($foundBib) . ",
par_date_bib = IFNULL(par_date_bib, NOW())
WHERE par_id = " . intval($par_id) . "
AND is_cancelled = 0
AND (no_bib IS NULL OR no_bib = '' OR no_bib = 0)
";
foreach ($tabRanges as $range) {
$objDatabase->fxQuery($sqlUpdate);
}
$start = (int)$range['start'];
$end = (int)$range['end'];
function fxApplyBatchTeamNumber($epr_id, $teamKey, $teamNumber, &$updatedTeams) {
global $objDatabase;
for ($i = $start; $i <= $end; $i++) {
// =====================
// SIMULATION
// =====================
if ($mode === 'simulation') {
if (!isset($usedBib[$i])) {
$foundBib = $i;
$usedBib[$i] = true; // réserver
break 2;
}
} else {
// =====================
// EXECUTE (sécurisé DB)
// =====================
$sqlCheck = "
SELECT COUNT(*)
FROM resultats_participants
WHERE epr_id = $epr_id
AND no_bib = $i
";
$exists = (int)$objDatabase->fxGetVar($sqlCheck);
if ($exists === 0) {
$foundBib = $i;
// ÉCRITURE RÉELLE
$sqlUpdate = "
UPDATE resultats_participants
SET no_bib = $i,
par_date_bib = IFNULL(par_date_bib, NOW())
WHERE par_id = $par_id
AND is_cancelled = 0
AND (no_bib IS NULL OR no_bib = '' OR no_bib = 0)
";
$objDatabase->fxQuery($sqlUpdate);
if ($teamNumber > 0 && !isset($updatedTeams[$teamKey])) {
if ($teamKey <= 0 || $teamNumber <= 0 || isset($updatedTeams[$teamKey])) {
return;
}
$sqlTeam = "
UPDATE resultats_epreuves_commandees
SET no_equipe = $teamNumber
WHERE pec_id_original = $teamKey
AND epr_id = $epr_id
SET no_equipe = " . intval($teamNumber) . "
WHERE pec_id_original = " . intval($teamKey) . "
AND epr_id = " . intval($epr_id) . "
";
$objDatabase->fxQuery($sqlTeam);
$updatedTeams[$teamKey] = true;
}
break 2;
}
}
}
} }
if (
$teamMode === 3
&& $teamKey > 0
&& $foundBib > 0
) {
if (!isset($teamBibMap[$teamKey])) {
function fxBuildBatchAssignmentRow($p, $foundBib, $teamNumber) {
return [
'rol_id' => $p['rol_id'],
'par_id' => (int)$p['par_id'],
'par_prenom' => $p['par_prenom'],
'par_nom' => $p['par_nom'],
'no_bib' => $foundBib,
'no_equipe' => $teamNumber,
'pec_nom_equipe' => $p['pec_nom_equipe'] ?? '',
];
}
$teamBibMap[$teamKey] = $foundBib;
function fxGetBatchTeamKey($p) {
return (int)($p['pec_id_original'] ?? $p['pec_id'] ?? 0);
}
$teamNumber = $foundBib;
function fxCountBatchAssignUnits($epr_id, $participants = null) {
global $objDatabase;
$epr_id = intval($epr_id);
if ($epr_id <= 0) {
return 0;
}
}
if (
$teamMode === 3
&& $teamKey > 0
&& isset($teamBibMap[$teamKey])
) {
$foundBib = $teamBibMap[$teamKey];
$teamNumber = $teamBibMap[$teamKey];
$sqlEpreuve = "
SELECT epr_equipe, epr_bib_team_mode
FROM inscriptions_epreuves
WHERE epr_id = $epr_id
LIMIT 1
";
$tabEpreuve = $objDatabase->fxGetRow($sqlEpreuve);
$countParticipants = function ($rows) {
return is_array($rows) ? count($rows) : 0;
};
if (!$tabEpreuve || (int)$tabEpreuve['epr_equipe'] !== 1) {
if (is_array($participants)) {
return $countParticipants($participants);
}
return (int)$objDatabase->fxGetVar("
SELECT COUNT(*)
FROM resultats_participants
WHERE epr_id = $epr_id
AND is_cancelled = 0
AND (no_bib IS NULL OR no_bib = '' OR no_bib = 0)
AND rol_id NOT IN (3, 4)
");
}
$teamMode = (int)($tabEpreuve['epr_bib_team_mode'] ?? 1);
if ($teamMode <= 0) {
$teamMode = 1;
}
if ($teamMode !== 3) {
if (is_array($participants)) {
return $countParticipants($participants);
}
return (int)$objDatabase->fxGetVar("
SELECT COUNT(*)
FROM resultats_participants
WHERE epr_id = $epr_id
AND is_cancelled = 0
AND (no_bib IS NULL OR no_bib = '' OR no_bib = 0)
AND rol_id NOT IN (3, 4)
");
}
if (is_array($participants)) {
$teams = [];
foreach ($participants as $p) {
$teamKey = fxGetBatchTeamKey($p);
if ($teamKey > 0) {
$teams[$teamKey] = true;
}
}
return count($teams);
}
return (int)$objDatabase->fxGetVar("
SELECT COUNT(DISTINCT p.pec_id)
FROM resultats_participants p
WHERE p.epr_id = $epr_id
AND p.is_cancelled = 0
AND (p.no_bib IS NULL OR p.no_bib = '' OR p.no_bib = 0)
AND p.rol_id NOT IN (3, 4)
AND p.pec_id > 0
");
}
// =====================
// PLUS DE PLACE → STOP
// =====================
function fxProcessBatchAssignmentsSolo($epr_id, $participants, $tabRanges, $mode = 'simulation') {
$assignments = [];
$usedBib = ($mode === 'simulation') ? fxLoadUsedBibsForBatch($epr_id) : [];
foreach ($participants as $p) {
$foundBib = fxFindNextBatchBib($epr_id, $tabRanges, $mode, $usedBib);
if ($foundBib === null) {
break;
}
if (
$teamMode === 3
&& $teamKey > 0
) {
$teamNumber = $foundBib;
}
// =====================
// STOCKER LE RÉSULTAT
// =====================
$assignments[] = [
'rol_id' => $p['rol_id'],
'par_id' => $par_id,
'par_prenom' => $p['par_prenom'],
'par_nom' => $p['par_nom'],
'no_bib' => $foundBib,
'no_equipe' => $teamNumber,
'pec_nom_equipe' => $p['pec_nom_equipe'] ?? '',
if ($mode === 'execute') {
fxApplyBatchParticipantBib($epr_id, (int)$p['par_id'], $foundBib);
}
];
$assignments[] = fxBuildBatchAssignmentRow($p, $foundBib, 0);
}
return $assignments;
}
function fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants) {
$nbParticipants = count($participants);
function fxProcessBatchAssignmentsEquipe($epr_id, $participants, $tabRanges, $mode, $teamMode) {
global $objDatabase;
$assignments = [];
$teamMap = [];
$updatedTeams = [];
// Mode 3 : mémorise le dossard déjà choisi pour chaque équipe (pec_id)
// → les coéquipiers réutilisent ce numéro sans repasser par fxFindNextBatchBib
$teamBibMap = [];
$nextTeamNumber = 1;
$usedBib = ($mode === 'simulation') ? fxLoadUsedBibsForBatch($epr_id) : [];
foreach ($participants as $p) {
$par_id = (int)$p['par_id'];
$teamKey = fxGetBatchTeamKey($p);
$teamNumber = 0;
$foundBib = null;
if ($teamMode === 3 && $teamKey > 0 && isset($teamBibMap[$teamKey])) {
// Coéquipier suivant : même dossard que le premier membre déjà traité
$foundBib = $teamBibMap[$teamKey];
$teamNumber = $foundBib;
} elseif ($teamMode !== 3 && $teamKey > 0) {
// Mode dossards individuels : no_equipe séquentiel (1, 2, 3…) partagé par l'équipe
if (!isset($teamMap[$teamKey])) {
$teamMap[$teamKey] = $nextTeamNumber;
$nextTeamNumber++;
}
$teamNumber = $teamMap[$teamKey];
}
if ($foundBib === null) {
// Premier membre d'une équipe (mode 3) ou chaque participant (mode 1)
$foundBib = fxFindNextBatchBib($epr_id, $tabRanges, $mode, $usedBib, $teamMode);
}
if ($foundBib === null) {
break;
}
if ($teamMode === 3 && $teamKey > 0) {
$teamBibMap[$teamKey] = $foundBib;
$teamNumber = $foundBib;
}
if ($mode === 'execute') {
fxApplyBatchParticipantBib($epr_id, $par_id, $foundBib);
fxApplyBatchTeamNumber($epr_id, $teamKey, $teamNumber, $updatedTeams);
}
$assignments[] = fxBuildBatchAssignmentRow($p, $foundBib, $teamNumber);
}
return $assignments;
}
function fxProcessBatchAssignments($epr_id, $participants, $tabRanges, $mode = 'simulation') {
global $objDatabase;
if (empty($participants) || empty($tabRanges)) {
return [];
}
$epr_id = intval($epr_id);
$sqlEpreuve = "
SELECT epr_equipe, epr_bib_team_mode
FROM inscriptions_epreuves
WHERE epr_id = $epr_id
LIMIT 1
";
$tabEpreuve = $objDatabase->fxGetRow($sqlEpreuve);
// Épreuves solo : logique simple, inchangée (1 personne = 1 dossard)
if (!$tabEpreuve || (int)$tabEpreuve['epr_equipe'] !== 1) {
return fxProcessBatchAssignmentsSolo($epr_id, $participants, $tabRanges, $mode);
}
// Épreuves équipe uniquement — voir fxProcessBatchAssignmentsEquipe()
$teamMode = (int)($tabEpreuve['epr_bib_team_mode'] ?? 1);
if ($teamMode <= 0) {
$teamMode = 1;
}
return fxProcessBatchAssignmentsEquipe($epr_id, $participants, $tabRanges, $mode, $teamMode);
}
function fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id = 0) {
$nbParticipants = fxCountBatchAssignUnits($epr_id, $participants);
$nbDispo = 0;
if (!empty($tabRangesInfos)) {