0 && fxBibProductionEventIsLocked($intProductionEveId)) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v5_production_locked_error'),
]);
exit;
}
}
// =====================
// CREATE
// =====================
if ($action == 'create') {
$epr_id = intval($_REQUEST['epr_id'] ?? 0);
if ($epr_id > 0) {
// ⚠️ On ne crée PLUS en DB
// MSIN-4536 — Stats lite (pas de CTE trous) pour CRUD / refresh UI.
$tabBibRanges = fxInfosBibRange($epr_id, 0, false);
// 👉 On ajoute UNE ligne vide (UI seulement)
$tabBibRanges[] = [
'epr_bib_id' => 0, // ID temporaire
'epr_id' => $epr_id,
'epr_bib_start' => '',
'epr_bib_finish' => '',
'dernier_bib' => '',
'nb_utilises' => '',
'liste_trous' => '',
'liste_doublons' => ''
];
// MSIN-4446 — summary_html pour pastille « N séquence(s) » hors du conteneur.
echo json_encode(array_merge([
'success' => true,
'html' => renderBibRanges($tabBibRanges, $epr_id),
'error' => ''
], fxBibAssignRangesAjaxExtras($epr_id, $tabBibRanges)));
exit;
}
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
// =====================
// DELETE
// =====================
if ($action == 'delete') {
$id = intval($_REQUEST['id'] ?? 0);
if ($id > 0) {
$rowLock = $objDatabase->fxGetRow("
SELECT epr_bib_locked
FROM inscriptions_epreuves_bib
WHERE epr_bib_id = $id
");
if ($rowLock && (int)($rowLock['epr_bib_locked'] ?? 0) === 1) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_range_locked')
]);
exit;
}
$range = fxInfosBibRange(0, $id, false);
$epr_id = $range[1]["epr_id"];
if (!$range) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_range_not_found')
]);
exit;
}
if ((int)$range[1]['nb_utilises'] > 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_delete_used')
]);
exit;
}
$sql = "
DELETE FROM inscriptions_epreuves_bib
WHERE epr_bib_id = $id
";
$objDatabase->fxQuery($sql);
$tabBibRanges = fxInfosBibRange($epr_id, 0, false);
// MSIN-4446 — Mettre à jour la pastille après suppression (ex. « Aucune séquence »).
echo json_encode(array_merge([
'success' => true,
'html' => renderBibRanges($tabBibRanges, $epr_id),
'error' => ''
], fxBibAssignRangesAjaxExtras($epr_id, $tabBibRanges)));
exit;
}
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_id_invalid')
]);
exit;
}
// =====================
// SAVE
// =====================
if ($action == 'save') {
$id = intval($_REQUEST['id'] ?? 0);
$epr_id = intval($_REQUEST['epr_id'] ?? 0);
$start = intval($_REQUEST['start'] ?? 0);
$end = intval($_REQUEST['end'] ?? 0);
$is_new = ($id == 0);
// =====================
// VALIDATION 1 — epr_id
// =====================
if ($is_new && $epr_id <= 0) {
echo json_encode(['success' => false, 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')]);
exit;
}
// =====================
// recherche eve_id
// =====================
$sqlGet = "SELECT eve_id FROM inscriptions_epreuves WHERE epr_id = $epr_id";
$eve_id = (int)$objDatabase->fxGetVar($sqlGet);
// =====================
// VALIDATION 2 — VALEURS NUMÉRIQUES
// =====================
if ($start <= 0 || $end <= 0) {
echo json_encode(['success' => false, 'message' => fxBibMsg('bib_v4_ajax_values_invalid')]);
exit;
}
// =====================
// VALIDATION 3 — LOGIQUE INTERVALLE
// =====================
if ($start > $end) {
echo json_encode(['success' => false, 'message' => fxBibMsg('bib_v4_ajax_start_gt_end')]);
exit;
}
// =====================
// RÉCUPÉRER epr_id + PROTÉGER LES DOSSARDS SI UPDATE
// =====================
if (!$is_new) {
$sqlGet = "SELECT epr_id, epr_bib_locked FROM inscriptions_epreuves_bib WHERE epr_bib_id = $id";
$rowRange = $objDatabase->fxGetRow($sqlGet);
if (!$rowRange || (int)($rowRange['epr_id'] ?? 0) <= 0) {
echo json_encode(['success' => false, 'message' => fxBibMsg('bib_v4_ajax_range_not_found')]);
exit;
}
if ((int)($rowRange['epr_bib_locked'] ?? 0) === 1) {
echo json_encode(['success' => false, 'message' => fxBibMsg('bib_v4_ajax_range_locked')]);
exit;
}
$epr_id = (int)$rowRange['epr_id'];
if ($epr_id <= 0) {
echo json_encode(['success' => false, 'message' => fxBibMsg('bib_v4_ajax_range_not_found')]);
exit;
}
$sqlBibNumRp = fxBibSqlNoBibUnsigned('rp.no_bib');
$sqlCheckOrphans = "
SELECT COUNT(*)
FROM resultats_participants rp
INNER JOIN inscriptions_epreuves_bib b
ON b.epr_bib_id = $id
WHERE rp.epr_id = b.epr_id
AND $sqlBibNumRp BETWEEN b.epr_bib_start AND b.epr_bib_finish
AND (
$sqlBibNumRp < $start
OR $sqlBibNumRp > $end
)
";
$nbOrphans = (int)$objDatabase->fxGetVar($sqlCheckOrphans);
if ($nbOrphans > 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_orphans')
]);
exit;
}
}
// =====================
// VALIDATION 4 — OVERLAP pas de double epreuves
// =====================
$tabOverlaps = fxBibGetOverlappingRanges($eve_id, $start, $end, $is_new ? 0 : $id, $epr_id);
if (!empty($tabOverlaps)) {
echo json_encode([
'success' => false,
'message' => fxBibFormatOverlapConflictMessage($tabOverlaps, $strLangue)
]);
exit;
}
// =====================
// INSERT OU UPDATE
// =====================
if ($is_new) {
$sql = "
INSERT INTO inscriptions_epreuves_bib
(epr_id, epr_bib_start, epr_bib_finish, update_date, update_qui, eve_id)
VALUES
($epr_id, $start, $end, NOW(), 'admin', $eve_id)
";
} else {
$sql = "
UPDATE inscriptions_epreuves_bib
SET epr_bib_start = $start,
epr_bib_finish = $end,
update_date = NOW(),
eve_id = $eve_id
WHERE epr_bib_id = $id
";
}
$objDatabase->fxQuery($sql);
// MSIN-4536 — Reload HTML en lite (trous = panneau anomalies seulement).
$tabBibRanges = fxInfosBibRange($epr_id, 0, false);
// MSIN-4446 — Pastille résumé après création / maj séquence.
echo json_encode(array_merge([
'success' => true,
'html' => renderBibRanges($tabBibRanges, $epr_id)
], fxBibAssignRangesAjaxExtras($epr_id, $tabBibRanges)));
exit;
}
// =====================
// VIEW
// =====================
if ($action == 'view') {
$epr_id = intval($_REQUEST['epr_id'] ?? 0);
$range_id = intval($_REQUEST['range_id'] ?? 0);
if ($epr_id <= 0 || $range_id < 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_params_invalid')
]);
exit;
}
// TEMPORAIRE : affichage simple pour test
$html = renderBibView($epr_id, $range_id, $strLangue);
echo json_encode([
'success' => true,
'html' => $html
]);
exit;
}
// =====================
// ANALYSE BATCH
// =====================
if ($action == 'batch_analysis') {
$epr_id = intval($_POST['epr_id'] ?? 0);
if ($epr_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
$infoBib = fxInfosBibEpreuve($epr_id);
echo json_encode([
'success' => true,
'info' => $infoBib
]);
exit;
}
// =====================
// MSIN-4424 — Réglage doublons inter-épreuves (événement)
// =====================
if ($action == 'save_allow_inter_epr') {
$int_eve_id = (int)($_POST['eve_id'] ?? 0);
$blnAllow = !empty($_POST['allow']) && (string)$_POST['allow'] !== '0';
if ($int_eve_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid'),
]);
exit;
}
$result = fxBibSetEventAllowsInterEprDuplicates($int_eve_id, $blnAllow);
if (empty($result['success'])) {
echo json_encode($result);
exit;
}
$strLangueAnom = $_SESSION['lang'] ?? 'fr';
$tabAnomalies = fxBibCollectAnomalies($int_eve_id, $strLangueAnom);
echo json_encode([
'success' => true,
'allow_inter_epr' => !empty($result['allow_inter_epr']) ? 1 : 0,
'anomalies_html' => renderBibAnomaliesPanel($int_eve_id, $strLangueAnom, $tabAnomalies),
'anomaly_count' => fxBibAnomalyTotalCount($tabAnomalies),
]);
exit;
}
// MSIN-4436 — Panneau assignation globale (chargement différé à l'ouverture).
if ($action == 'global_batch_panel') {
$int_eve_id = (int)($_POST['eve_id'] ?? 0);
if ($int_eve_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid'),
]);
exit;
}
$tabEpreuves = $objDatabase->fxGetResults(
"SELECT * FROM inscriptions_epreuves e WHERE e.eve_id = " . $int_eve_id . " AND epr_actif = 1 ORDER BY epr_id"
);
$tabBibAssignments = $objDatabase->fxGetResults(
"SELECT * FROM bib_assignements WHERE ba_actif = 1 ORDER BY ba_tri"
);
$html = renderBibGlobalBatchPanel($int_eve_id, $tabEpreuves, $tabBibAssignments, $strLangue);
if ($html === '') {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_global_batch_unavailable'),
]);
exit;
}
echo json_encode([
'success' => true,
'html' => $html,
]);
exit;
}
// MSIN-4436 — Analyse batch global (épreuves solo, séquences par épreuve).
if ($action == 'batch_global_analysis') {
$int_eve_id = (int)($_POST['eve_id'] ?? 0);
$tabSorts = fxBibParseBatchSortFromPost();
$strSort1 = $tabSorts[0] ?? '';
$strSort2 = $tabSorts[1] ?? '';
$plans = json_decode($_POST['plans'] ?? '[]', true);
$tabPlans = fxBibParseGlobalBatchPlans($plans, $int_eve_id);
if ($int_eve_id <= 0 || $strSort1 === '' || empty($tabPlans)) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_global_batch_plan_invalid'),
]);
exit;
}
$intParticipants = 0;
$intDispo = 0;
$intAssignable = 0;
$intManque = 0;
foreach ($tabPlans as $plan) {
$epr_id = (int)$plan['epr_id'];
$ranges = $plan['ranges'];
$participants = fxGetParticipantsForBatchAssign($epr_id, $tabSorts);
// Perf — résumé = dispo/utilisés seulement (pas le calcul des trous).
$tabRangesInfos = fxInfosBibRange($epr_id, 0, false);
$ids = array_map('intval', $ranges);
$tabRangesInfos = array_filter($tabRangesInfos, function ($r) use ($ids) {
return in_array((int)$r['epr_bib_id'], $ids, true);
});
$summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id);
$intParticipants += (int)$summary['nb_participants'];
$intDispo += (int)$summary['nb_dispo'];
$intAssignable += (int)$summary['nb_assignable'];
$intManque += (int)$summary['nb_manque'];
}
$info = fxBibMsg(
'bib_v4_ajax_global_sim_summary',
count($tabPlans),
$intDispo,
$intParticipants,
$intAssignable,
$intManque
);
echo json_encode([
'success' => true,
'info' => $info,
'stats' => [
'nb_epreuves' => count($tabPlans),
'nb_dispo' => $intDispo,
'nb_participants' => $intParticipants,
'nb_assignable' => $intAssignable,
'nb_manque' => $intManque,
],
]);
exit;
}
// MSIN-4436 — Simulation batch global.
if ($action == 'batch_global_simulate') {
$int_eve_id = (int)($_POST['eve_id'] ?? 0);
$tabSorts = fxBibParseBatchSortFromPost();
$strSort1 = $tabSorts[0] ?? '';
$strSort2 = $tabSorts[1] ?? '';
$plans = json_decode($_POST['plans'] ?? '[]', true);
$tabPlans = fxBibParseGlobalBatchPlans($plans, $int_eve_id);
if ($int_eve_id <= 0 || $strSort1 === '' || empty($tabPlans)) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_global_batch_plan_invalid'),
]);
exit;
}
$htmlParts = [];
$intParticipants = 0;
$intDispo = 0;
$intAssignable = 0;
$intManque = 0;
foreach ($tabPlans as $plan) {
$epr_id = (int)$plan['epr_id'];
$ranges = $plan['ranges'];
$participants = fxGetParticipantsForBatchAssign($epr_id, $tabSorts);
$tabRanges = fxGetRangesForBatchAssign($epr_id, $ranges);
$simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation');
$tabRangesInfos = fxInfosBibRange($epr_id, 0, false);
$ids = array_map('intval', $ranges);
$tabRangesInfos = array_filter($tabRangesInfos, function ($r) use ($ids) {
return in_array((int)$r['epr_bib_id'], $ids, true);
});
$summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id);
$intParticipants += (int)$summary['nb_participants'];
$intDispo += (int)$summary['nb_dispo'];
$intAssignable += (int)$summary['nb_assignable'];
$intManque += (int)$summary['nb_manque'];
$tabEpreuve = fxGetEpreuve($epr_id);
$strEprLabel = $tabEpreuve ? fxBibEpreuveDisplayName($tabEpreuve, $strLangue) : ('#' . $epr_id);
$htmlParts[] = '
'
. '
' . fxBibEsc($strEprLabel) . '
'
. renderBibView(
$epr_id,
0,
$strLangue,
'simulation',
$simu,
fxBibTexte('bib_v4_ajax_title_simulation'),
fxBibMsg(
'bib_v4_ajax_sim_summary',
$summary['nb_dispo'],
$summary['nb_participants'],
$summary['nb_manque']
)
)
. '';
}
$info = fxBibMsg(
'bib_v4_ajax_global_sim_summary',
count($tabPlans),
$intDispo,
$intParticipants,
$intAssignable,
$intManque
);
$html = fxBibWrapGlobalSimView($info, implode('', $htmlParts));
echo json_encode([
'success' => true,
'html' => $html,
'info' => $info,
]);
exit;
}
// MSIN-4443 — Analyse batch global sans séquence (plages générées).
// MSIN-4445 — end_bib optionnel (0 = taille auto = à assigner).
if ($action == 'batch_global_gen_analysis') {
$int_eve_id = (int)($_POST['eve_id'] ?? 0);
$intStartBib = (int)($_POST['start_bib'] ?? 0);
$intEndBib = (int)($_POST['end_bib'] ?? 0);
$tabSorts = fxBibParseBatchSortFromPost();
$strSort1 = $tabSorts[0] ?? '';
$strSort2 = $tabSorts[1] ?? '';
$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;
}
if ($intEndBib > 0 && $intEndBib < $intStartBib) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'),
]);
exit;
}
$tabPlan = fxBibBuildGeneratedRangePlan(
$int_eve_id,
$tabEprIds,
$intStartBib,
$tabSorts,
'',
$intEndBib
);
$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;
}
$intRangeEnd = (int)$item['end'];
}
$info = fxBibMsg(
'bib_v4_ajax_global_gen_analysis',
$tabStats['nb_epreuves'],
$intRangeStart,
$intRangeEnd,
$tabStats['nb_participants']
);
$tabFeedback = null;
$tabDupes = ['has' => false, 'count' => 0, 'message' => ''];
if ($intEndBib >= $intStartBib && $intEndBib > 0) {
$tabFeedback = fxBibGeneratedQtyFeedback($intEndBib - $intStartBib + 1, $tabStats['nb_participants']);
$tabDupes = fxBibGeneratedDuplicateRisk($int_eve_id, $tabPlan, $intStartBib, $intEndBib);
}
echo json_encode([
'success' => true,
'info' => $info,
'stats' => $tabStats,
'feedback' => $tabFeedback,
'duplicates' => $tabDupes,
]);
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);
$intEndBib = (int)($_POST['end_bib'] ?? 0);
$tabSorts = fxBibParseBatchSortFromPost();
$strSort1 = $tabSorts[0] ?? '';
$strSort2 = $tabSorts[1] ?? '';
$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;
}
if ($intEndBib > 0 && $intEndBib < $intStartBib) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'),
]);
exit;
}
$tabPlan = fxBibBuildGeneratedRangePlan(
$int_eve_id,
$tabEprIds,
$intStartBib,
$tabSorts,
'',
$intEndBib
);
$result = fxBibSimulateGlobalBatchGenerated($int_eve_id, $tabPlan, $tabSorts, '', $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);
$intEndBib = (int)($_POST['end_bib'] ?? 0);
$tabSorts = fxBibParseBatchSortFromPost();
$strSort1 = $tabSorts[0] ?? '';
$strSort2 = $tabSorts[1] ?? '';
$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;
}
if ($intEndBib > 0 && $intEndBib < $intStartBib) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_global_batch_gen_plan_invalid'),
]);
exit;
}
$tabPlan = fxBibBuildGeneratedRangePlan(
$int_eve_id,
$tabEprIds,
$intStartBib,
$tabSorts,
'',
$intEndBib
);
$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);
$mode = $_POST['mode'] ?? $_GET['mode'] ?? 'assign';
$blnLite = !empty($_POST['lite']) || !empty($_GET['lite']);
if ($epr_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
$tabBibStats = fxInfosBibRange($epr_id, 0, $blnLite ? false : true);
// MSIN-4446 — Pastille résumé synchronisée avec le refresh des plages.
echo json_encode(array_merge([
'success' => true,
'html' => renderBibRanges($tabBibStats, $epr_id, $mode)
], fxBibAssignRangesAjaxExtras($epr_id, $tabBibStats)));
exit;
}
if ($action == 'refresh_ranges_data') {
$epr_id = intval($_POST['epr_id'] ?? 0);
if ($epr_id <= 0) {
echo json_encode(['success' => false]);
exit;
}
$tab = fxInfosBibRange($epr_id, 0, false);
$out = [];
foreach ($tab as $r) {
$nb_total = $r['nb_total_range'] ?? 0;
$nb_used = (int)($r['nb_utilises'] ?? 0);
$out[] = [
'id' => (int)$r['epr_bib_id'],
'last' => (int)$r['dernier_bib'],
'used' => $nb_used,
'dispo' => $nb_total - $nb_used,
'trous' => $r['liste_trous'] ?? '',
'doublons' => $r['liste_doublons'] ?? ''
];
}
echo json_encode([
'success' => true,
'ranges' => $out
]);
exit;
}
// =====================
// BATCH GO (DEBUG SEULEMENT)
// =====================
// Objectif :
// - recevoir les données du bouton GO
// - NE RIEN ÉCRIRE en DB
// - retourner les infos pour validation
if ($action == 'batch_go') {
// ID de l’épreuve
$epr_id = intval($_POST['epr_id'] ?? 0);
// Liste des ranges sélectionnés (JSON → array PHP)
$ranges = json_decode($_POST['ranges'] ?? '[]', true);
$lockCheck = fxBibAssertRangesUnlocked($epr_id, is_array($ranges) ? $ranges : []);
if (!$lockCheck['success']) {
echo json_encode([
'success' => false,
'message' => $lockCheck['message'] ?? fxBibMsg('bib_v4_ajax_range_locked')
]);
exit;
}
// MSIN-4379 — Mode équipe affiché à l'UI (évite course avec save_team_mode async).
fxBibSyncTeamModeFromRequest($epr_id);
$tabSorts = fxBibParseBatchSortFromPost();
$strSort1 = $tabSorts[0] ?? '';
$strSort2 = $tabSorts[1] ?? '';
fxBibSaveEpreuveBatchSort($epr_id, $tabSorts);
$participants = fxGetParticipantsForBatchAssign($epr_id, $tabSorts);
// =====================
// UTILISER LA FONCTION PROPRE
// =====================
$tabRanges = fxGetRangesForBatchAssign($epr_id, $ranges);
// Perf — résumé = dispo/utilisés seulement (pas le calcul des trous 0–9999).
$tabRangesInfos = fxInfosBibRange($epr_id, 0, false);
$ids = array_map('intval', $ranges);
$tabRangesInfos = array_filter($tabRangesInfos, function($r) use ($ids) {
return in_array((int)$r['epr_bib_id'], $ids);
});
// Perf — fxGetNextAvailableBib (N COUNT SQL) n'était jamais utilisé dans la réponse.
$simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation');
$summary = fxBuildBatchSummaryFromRanges($tabRangesInfos, $participants, $epr_id);
$info = fxBibMsg(
'bib_v4_ajax_sim_summary',
$summary['nb_dispo'],
$summary['nb_participants'],
$summary['nb_manque']
);
// MSIN-4533 — Simulation complète en mémoire ; HTML = échantillon seulement.
list($tabView, $info) = fxBibCapViewSample($simu, $info, 100);
$html = renderBibView(
$epr_id,
0,
$strLangue,
'simulation',
$tabView,
fxBibTexte('bib_v4_ajax_title_simulation'),
$info
);
echo json_encode([
'success' => true,
'html' => $html,
'count' => count($simu),
]);
exit;
}
if ($action == 'batch_apply') {
// =====================
// GO BATCH (EXÉCUTION RÉELLE)
// - Assigne les BIB en DB
// - Utilise fxProcessBatchAssignments en mode 'execute'
// - Affiche le résultat réel ($exec), pas de simulation
// =====================
$epr_id = intval($_POST['epr_id'] ?? 0);
$ranges = json_decode($_POST['ranges'] ?? '[]', true);
$tabSorts = fxBibParseBatchSortFromPost();
$strSort1 = $tabSorts[0] ?? '';
$strSort2 = $tabSorts[1] ?? '';
$lockCheck = fxBibAssertRangesUnlocked($epr_id, is_array($ranges) ? $ranges : []);
if (!$lockCheck['success']) {
echo json_encode([
'success' => false,
'message' => $lockCheck['message'] ?? fxBibMsg('bib_v4_ajax_range_locked')
]);
exit;
}
// MSIN-4379 — Mode équipe affiché à l'UI (évite course avec save_team_mode async).
fxBibSyncTeamModeFromRequest($epr_id);
fxBibSaveEpreuveBatchSort($epr_id, $tabSorts);
$participants = fxGetParticipantsForBatchAssign($epr_id, $tabSorts);
$tabRanges = fxGetRangesForBatchAssign($epr_id, $ranges);
$exec = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'execute');
$html = fxBibBuildBatchApplyHtml($epr_id, $exec, count($participants), $strLangue);
echo json_encode([
'success' => true,
'html' => $html
]);
exit;
}
// MSIN-4433 — GO batch par paquets : progression réelle + relecture BD entre paquets.
if ($action == 'batch_apply_chunk') {
$epr_id = intval($_POST['epr_id'] ?? 0);
$ranges = json_decode($_POST['ranges'] ?? '[]', true);
$tabSorts = fxBibParseBatchSortFromPost();
$strSort1 = $tabSorts[0] ?? '';
$strSort2 = $tabSorts[1] ?? '';
$intChunkSize = (int)($_POST['chunk_size'] ?? 50);
$intTotalInitial = (int)($_POST['total_initial'] ?? 0);
$blnBatchReset = ((int)($_POST['batch_reset'] ?? 0) === 1);
if ($intChunkSize < 1) {
$intChunkSize = 50;
}
if ($intChunkSize > 100) {
$intChunkSize = 100;
}
$lockCheck = fxBibAssertRangesUnlocked($epr_id, is_array($ranges) ? $ranges : []);
if (!$lockCheck['success']) {
echo json_encode([
'success' => false,
'message' => $lockCheck['message'] ?? fxBibMsg('bib_v4_ajax_range_locked')
]);
exit;
}
// MSIN-4379 — Mode équipe affiché à l'UI (évite course avec save_team_mode async).
fxBibSyncTeamModeFromRequest($epr_id);
$strSessionKey = fxBibBatchExecSessionKey($epr_id);
if ($blnBatchReset) {
$_SESSION[$strSessionKey] = [];
fxBibSaveEpreuveBatchSort($epr_id, $tabSorts);
} elseif (!isset($_SESSION[$strSessionKey]) || !is_array($_SESSION[$strSessionKey])) {
$_SESSION[$strSessionKey] = [];
}
$participants = fxGetParticipantsForBatchAssign($epr_id, $tabSorts);
if ($intTotalInitial <= 0) {
$intTotalInitial = count($participants) + count($_SESSION[$strSessionKey]);
}
if (empty($participants)) {
$execAll = $_SESSION[$strSessionKey];
unset($_SESSION[$strSessionKey]);
$html = fxBibBuildBatchApplyHtml($epr_id, $execAll, $intTotalInitial, $strLangue);
echo json_encode([
'success' => true,
'complete' => true,
'total_initial' => $intTotalInitial,
'processed' => $intTotalInitial,
'assigned_total' => count($execAll),
'assigned_chunk' => 0,
'html' => $html
]);
exit;
}
$tabChunk = array_slice($participants, 0, $intChunkSize);
$tabRanges = fxGetRangesForBatchAssign($epr_id, $ranges);
$execChunk = fxProcessBatchAssignments($epr_id, $tabChunk, $tabRanges, 'execute');
$_SESSION[$strSessionKey] = array_merge($_SESSION[$strSessionKey], $execChunk);
$intRemaining = count(fxGetParticipantsForBatchAssign($epr_id, $tabSorts));
$intProcessed = max(0, $intTotalInitial - $intRemaining);
$blnComplete = ($intRemaining === 0);
// MSIN-4433 — Dossards épuisés : clôturer si aucune progression sur ce paquet.
if (!$blnComplete && empty($execChunk)) {
$blnComplete = true;
}
$arrResponse = [
'success' => true,
'complete' => $blnComplete,
'total_initial' => $intTotalInitial,
'processed' => $intProcessed,
'assigned_total' => count($_SESSION[$strSessionKey]),
'assigned_chunk' => count($execChunk),
];
if ($blnComplete) {
$execAll = $_SESSION[$strSessionKey];
unset($_SESSION[$strSessionKey]);
$arrResponse['html'] = fxBibBuildBatchApplyHtml($epr_id, $execAll, $intTotalInitial, $strLangue);
}
echo json_encode($arrResponse);
exit;
}
// =====================
// RESET PREVIEW
// =====================
if ($action == 'reset_preview') {
$epr_id = intval($_POST['epr_id'] ?? 0);
$ranges = json_decode($_POST['ranges'] ?? '[]', true);
$lockCheck = fxBibAssertRangesUnlocked($epr_id, is_array($ranges) ? $ranges : []);
if (!$lockCheck['success']) {
echo json_encode([
'success' => false,
'message' => $lockCheck['message'] ?? fxBibMsg('bib_v4_ajax_range_locked')
]);
exit;
}
$participants = fxGetParticipantsForBatchReset($epr_id, $ranges);
$intTotal = is_array($participants) ? count($participants) : 0;
$strInfo = fxBibMsg('bib_v4_ajax_reset_preview_count', $intTotal);
// MSIN-4533 — Compteur sur le total ; HTML = échantillon.
list($tabView, $strInfo) = fxBibCapViewSample(
is_array($participants) ? $participants : [],
$strInfo,
100
);
$html = renderBibView(
$epr_id,
0,
$strLangue,
'reset',
$tabView,
fxBibTexte('bib_v4_ajax_title_reset_preview'),
$strInfo,
'warning'
);
echo json_encode([
'success' => true,
'html' => $html,
'count' => $intTotal,
]);
exit;
}
// =====================
// RESET APPLY
// MSIN-4528 / MSIN-4529 — UPDATE bulk + réponse compacte (pas de grille HTML).
// =====================
if ($action == 'reset_apply') {
$epr_id = intval($_POST['epr_id'] ?? 0);
$ranges = json_decode($_POST['ranges'] ?? '[]', true);
$lockCheck = fxBibAssertRangesUnlocked($epr_id, is_array($ranges) ? $ranges : []);
if (!$lockCheck['success']) {
echo json_encode([
'success' => false,
'message' => $lockCheck['message'] ?? fxBibMsg('bib_v4_ajax_range_locked')
]);
exit;
}
$count = fxBibBulkResetBibsByRanges($epr_id, is_array($ranges) ? $ranges : []);
echo json_encode([
'success' => true,
'count' => $count,
'message' => fxBibMsg('bib_v4_ajax_reset_done_count', $count),
'html' => '',
]);
exit;
}
// =====================
// RESET ALL ÉPREUVE
// MSIN-4528 / MSIN-4529 — UPDATE bulk + réponse compacte.
// =====================
if ($action == 'reset_all_apply') {
$epr_id = intval($_POST['epr_id'] ?? 0);
if ($epr_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_params_invalid'),
]);
exit;
}
$lockCheck = fxBibAssertEpreuveNoLockedRanges($epr_id);
if (!$lockCheck['success']) {
echo json_encode([
'success' => false,
'message' => $lockCheck['message'] ?? fxBibMsg('bib_v4_js_reset_all_locked'),
]);
exit;
}
$count = fxBibBulkResetBibsEpreuve($epr_id);
echo json_encode([
'success' => true,
'count' => $count,
'message' => fxBibMsg('bib_v4_ajax_reset_done_count', $count),
'html' => '',
]);
exit;
}
// =====================
// SAVE TEAM MODE
// =====================
if ($action == 'save_team_mode') {
$epr_id = intval($_REQUEST['epr_id'] ?? 0);
$team_mode = intval($_REQUEST['team_mode'] ?? 0);
if ($epr_id <= 0 || !in_array($team_mode, [1, 2, 3])) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_params_invalid')
]);
exit;
}
$sql = "
UPDATE inscriptions_epreuves
SET epr_bib_team_mode = $team_mode
WHERE epr_id = $epr_id
LIMIT 1
";
$objDatabase->fxQuery($sql);
echo json_encode([
'success' => true
]);
exit;
}
// MSIN-4379 — Verrouillage séquence (cadenas ouvert/fermé)
if ($action == 'toggle_lock') {
$epr_bib_id = intval($_POST['epr_bib_id'] ?? 0);
$epr_id = intval($_POST['epr_id'] ?? 0);
$locked = intval($_POST['locked'] ?? 0) === 1;
$result = fxBibSetRangeLocked($epr_bib_id, $locked, $epr_id);
if (!$result['success']) {
echo json_encode([
'success' => false,
'message' => $result['message'] ?? fxBibMsg('bib_v4_ajax_error')
]);
exit;
}
$epr_id = (int)$result['epr_id'];
$tabBibStats = fxInfosBibRange($epr_id, 0, false);
$tabExtras = fxBibAssignRangesAjaxExtras($epr_id, $tabBibStats);
echo json_encode(array_merge([
'success' => true,
'locked' => !empty($result['locked']),
'html' => renderBibRanges($tabBibStats, $epr_id, 'assign')
], $tabExtras));
exit;
}
// MSIN-4379 — Étape 2
// Endpoint AJAX : sauvegarde ou désactivation de la config auto (epr_bib_auto).
// Paramètres : epr_id, active (0|1), ranges (JSON des epr_bib_id), csrf_token.
// Prochain tour : étape 6–7 (assignation réelle à l'achat, sans passer par cet endpoint).
if ($action == 'save_auto_config') {
$epr_id = intval($_POST['epr_id'] ?? 0);
$active = intval($_POST['active'] ?? 1) === 1;
$ranges = json_decode($_POST['ranges'] ?? '[]', true);
if ($epr_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
$result = fxSaveBibAutoConfig($epr_id, is_array($ranges) ? $ranges : [], $active);
if (!$result['success']) {
echo json_encode([
'success' => false,
'message' => $result['message'] ?? fxBibMsg('bib_v4_ajax_error')
]);
exit;
}
$tabBibStats = fxInfosBibRange($epr_id, 0, false);
$tabExtras = fxBibAssignRangesAjaxExtras($epr_id, $tabBibStats);
// Conserver le booléen métier de la config auto (prioritaire sur le recalcul).
$tabExtras['auto_active'] = !empty($result['auto_active']) ? 1 : 0;
$tabExtras['summary_html'] = fxBibAssignHeaderSummary(
(int)$tabExtras['seq_count'],
!empty($result['auto_active'])
);
echo json_encode(array_merge([
'success' => true,
'html' => renderBibRanges($tabBibStats, $epr_id, 'assign')
], $tabExtras));
exit;
}
// MSIN-4379 — Dashboard anomalies (dossards en double, orphelins, etc.)
if ($action == 'anomalies') {
$int_eve_id = (int)($_POST['eve_id'] ?? $_GET['eve_id'] ?? 0);
if ($int_eve_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
$tabAnomalies = fxBibCollectAnomalies($int_eve_id, $strLangue);
echo json_encode([
'success' => true,
'count' => fxBibAnomalyTotalCount($tabAnomalies),
'html' => renderBibAnomaliesPanel($int_eve_id, $strLangue, $tabAnomalies),
]);
exit;
}
// MSIN-4546 — Onglet Production (chargé seulement à l'ouverture de l'espace).
if ($action == 'production_panel') {
$int_eve_id = (int)($_POST['eve_id'] ?? $_GET['eve_id'] ?? 0);
if ($int_eve_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid'),
]);
exit;
}
global $objDatabase;
$tabEpreuves = $objDatabase->fxGetResults(
"SELECT * FROM inscriptions_epreuves e
WHERE e.eve_id = $int_eve_id AND e.epr_actif = 1
ORDER BY e.epr_id"
);
if (!is_array($tabEpreuves)) {
$tabEpreuves = [];
}
$tabShellStats = fxBibCollectEventShellStats($int_eve_id);
echo json_encode([
'success' => true,
'html' => renderBibProductionPreview($tabEpreuves, $strLangue, $tabShellStats),
]);
exit;
}
// MSIN-4546 — Détail éditable d'une épreuve (ouverture carte).
if ($action == 'race_detail') {
$epr_id = (int)($_POST['epr_id'] ?? $_GET['epr_id'] ?? 0);
$int_eve_id = (int)($_POST['eve_id'] ?? $_GET['eve_id'] ?? 0);
if ($epr_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid'),
]);
exit;
}
$tabEpreuve = fxGetEpreuve($epr_id);
if ($tabEpreuve == null) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid'),
]);
exit;
}
if ($int_eve_id > 0 && (int)($tabEpreuve['eve_id'] ?? 0) !== $int_eve_id) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid'),
]);
exit;
}
if ($int_eve_id <= 0) {
$int_eve_id = (int)($tabEpreuve['eve_id'] ?? 0);
}
echo json_encode([
'success' => true,
'html' => renderBibRaceDetailHtml($tabEpreuve, $int_eve_id, $strLangue),
'auto_active' => fxIsBibAutoActive($epr_id) ? 1 : 0,
'a_assigner' => (int)(fxInfosBibEpreuve($epr_id)['a_assigner'] ?? 0),
]);
exit;
}
// MSIN-4424 — Sommaire des quantités (toutes épreuves)
if ($action == 'qty_summary') {
$int_eve_id = (int)($_POST['eve_id'] ?? $_GET['eve_id'] ?? 0);
if ($int_eve_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
$tabSummary = fxBibCollectEventQtySummary($int_eve_id, $strLangue);
echo json_encode([
'success' => true,
'totals' => $tabSummary['totals'] ?? [],
'html' => renderBibQtySummaryPanel($int_eve_id, $strLangue, $tabSummary),
]);
exit;
}
// MSIN-4433 — Panneau impression PDF distribution dossards
if ($action == 'dist_print_panel') {
$int_eve_id = (int)($_POST['eve_id'] ?? $_GET['eve_id'] ?? 0);
if ($int_eve_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
$tabPanel = fxBibCollectDistPrintPanelData($int_eve_id, $strLangue);
echo json_encode([
'success' => true,
'html' => renderBibDistPrintPanel($int_eve_id, $strLangue, $tabPanel),
'meta' => $tabPanel['meta'] ?? [],
]);
exit;
}
// MSIN-4515 — Enregistrer Face / Endos / Info pour l’Excel imprimeur
if ($action == 'production_fields_save') {
$int_eve_id = (int)($_POST['eve_id'] ?? 0);
if ($int_eve_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid'),
]);
exit;
}
if (!fxBibProductionUserCanManageEvent($int_eve_id)) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_unauthorized'),
]);
exit;
}
$tabSelections = fxBibProductionParseFieldsFromPost($_POST);
if (empty($tabSelections) && isset($_POST['prod_fields']) && is_array($_POST['prod_fields'])) {
// Compat ancien format : épreuves présentes avec tableau vide.
foreach ($_POST['prod_fields'] as $mixEprId => $tabIgnored) {
$intEprId = (int)$mixEprId;
if ($intEprId > 0 && !isset($tabSelections[$intEprId])) {
$tabSelections[$intEprId] = [];
}
}
}
$tabResult = fxBibProductionSaveFields($int_eve_id, $tabSelections, $strLangue);
echo json_encode($tabResult);
exit;
}
// =====================
// ACTION INVALIDE
// =====================
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_action_invalid')
]);
exit;