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 // On récupère les ranges existants AVEC stats $tabBibRanges = fxInfosBibRange($epr_id); // 👉 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); $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); // 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); // ===================== // RELOAD HTML // ===================== $tabBibRanges = fxInfosBibRange($epr_id); // 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); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $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, $strSort1, $strSort2); $tabRangesInfos = fxInfosBibRange($epr_id); $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); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $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, $strSort1, $strSort2); $tabRanges = fxGetRangesForBatchAssign($epr_id, $ranges); $simu = fxProcessBatchAssignments($epr_id, $participants, $tabRanges, 'simulation'); $tabRangesInfos = fxInfosBibRange($epr_id); $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). if ($action == 'batch_global_gen_analysis') { $int_eve_id = (int)($_POST['eve_id'] ?? 0); $intStartBib = (int)($_POST['start_bib'] ?? 0); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $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; } $tabPlan = fxBibBuildGeneratedRangePlan($int_eve_id, $tabEprIds, $intStartBib, $strSort1, $strSort2); $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; } if ($intRangeEnd < (int)$item['start']) { $intRangeEnd = (int)$item['end']; } else { $intRangeEnd = (int)$item['end']; } } $info = fxBibMsg( 'bib_v4_ajax_global_gen_analysis', $tabStats['nb_epreuves'], $intRangeStart, $intRangeEnd, $tabStats['nb_participants'] ); echo json_encode([ 'success' => true, 'info' => $info, 'stats' => $tabStats, ]); 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); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $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; } $tabPlan = fxBibBuildGeneratedRangePlan($int_eve_id, $tabEprIds, $intStartBib, $strSort1, $strSort2); $result = fxBibSimulateGlobalBatchGenerated($int_eve_id, $tabPlan, $strSort1, $strSort2, $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); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $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; } $tabPlan = fxBibBuildGeneratedRangePlan($int_eve_id, $tabEprIds, $intStartBib, $strSort1, $strSort2); $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; } // MSIN-4445 — Analyse batch global libre (Début/Fin, sans séquence). if ($action == 'batch_global_free_analysis') { $int_eve_id = (int)($_POST['eve_id'] ?? 0); $intStart = (int)($_POST['start_bib'] ?? 0); $intEnd = (int)($_POST['end_bib'] ?? 0); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $eprIds = json_decode($_POST['epr_ids'] ?? '[]', true); $tabEprIds = fxBibParseGlobalBatchFreeEprIds(is_array($eprIds) ? $eprIds : [], $int_eve_id); if ($int_eve_id <= 0 || $intStart <= 0 || $intEnd < $intStart) { echo json_encode([ 'success' => false, 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), 'duplicates' => ['has' => false, 'count' => 0, 'message' => ''], ]); exit; } // MSIN-4445 — Doublons basés sur la plage entière (même si tri / épreuves incomplets). $tabDupesOnly = fxBibFreeDuplicateRisk($int_eve_id, [], $intStart, $intEnd); if ($strSort1 === '') { $strSort1 = 'ba:2'; } if (empty($tabEprIds)) { echo json_encode([ 'success' => true, 'info' => $tabDupesOnly['message'], 'stats' => ['nb_epreuves' => 0, 'nb_participants' => 0, 'nb_bibs' => 0], 'feedback' => fxBibFreeQtyFeedback($intEnd - $intStart + 1, 0), 'duplicates' => $tabDupesOnly, ]); exit; } $tabPlan = fxBibBuildFreeRangePlan($int_eve_id, $tabEprIds, $intStart, $intEnd, $strSort1, $strSort2); $tabAnalysis = fxBibBuildGlobalBatchFreeAnalysis($int_eve_id, $tabPlan, $intStart, $intEnd); echo json_encode([ 'success' => true, 'info' => $tabAnalysis['info'], 'stats' => $tabAnalysis['stats'], 'feedback' => $tabAnalysis['feedback'], 'duplicates' => $tabAnalysis['duplicates'], ]); exit; } // MSIN-4445 — Simulation batch global libre. if ($action == 'batch_global_free_simulate') { $int_eve_id = (int)($_POST['eve_id'] ?? 0); $intStart = (int)($_POST['start_bib'] ?? 0); $intEnd = (int)($_POST['end_bib'] ?? 0); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $eprIds = json_decode($_POST['epr_ids'] ?? '[]', true); $tabEprIds = fxBibParseGlobalBatchFreeEprIds(is_array($eprIds) ? $eprIds : [], $int_eve_id); if ($int_eve_id <= 0 || $intStart <= 0 || $intEnd < $intStart || $strSort1 === '' || empty($tabEprIds)) { echo json_encode([ 'success' => false, 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), ]); exit; } $tabPlan = fxBibBuildFreeRangePlan($int_eve_id, $tabEprIds, $intStart, $intEnd, $strSort1, $strSort2); $result = fxBibSimulateGlobalBatchFree( $int_eve_id, $tabPlan, $intStart, $intEnd, $strSort1, $strSort2, $strLangue ); echo json_encode($result); exit; } // MSIN-4445 — Prépare les plans GO libres (aucune création de séquence). if ($action == 'batch_global_free_plan') { $int_eve_id = (int)($_POST['eve_id'] ?? 0); $intStart = (int)($_POST['start_bib'] ?? 0); $intEnd = (int)($_POST['end_bib'] ?? 0); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $eprIds = json_decode($_POST['epr_ids'] ?? '[]', true); $tabEprIds = fxBibParseGlobalBatchFreeEprIds(is_array($eprIds) ? $eprIds : [], $int_eve_id); if ($int_eve_id <= 0 || $intStart <= 0 || $intEnd < $intStart || $strSort1 === '' || empty($tabEprIds)) { echo json_encode([ 'success' => false, 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), ]); exit; } $tabPlan = fxBibBuildFreeRangePlan($int_eve_id, $tabEprIds, $intStart, $intEnd, $strSort1, $strSort2); $prepare = fxBibPrepareFreeRangePlans($int_eve_id, $tabPlan, $intStart, $intEnd, $strLangue); echo json_encode($prepare); 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); $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; } list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); fxBibSaveEpreuveBatchSort($epr_id, $strSort1, $strSort2); $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); // ===================== // UTILISER LA FONCTION PROPRE // ===================== $tabRanges = fxGetRangesForBatchAssign($epr_id, $ranges); $tabRangesInfos = fxInfosBibRange($epr_id); // ← ICI $ids = array_map('intval', $ranges); $tabRangesInfos = array_filter($tabRangesInfos, function($r) use ($ids) { return in_array((int)$r['epr_bib_id'], $ids); }); $nextBib = fxGetNextAvailableBib($epr_id, $tabRanges); $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'] ); // ===================== // SIMULATION (temporaire simple) // ===================== // ===================== // RENDER VIEW EN MODE SIMULATION // ===================== $html = renderBibView( $epr_id, 0, $strLangue, 'simulation', $simu, fxBibTexte('bib_v4_ajax_title_simulation'), $info ); echo json_encode([ 'success' => true, 'html' => $html ]); 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); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $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; } fxBibSaveEpreuveBatchSort($epr_id, $strSort1, $strSort2); $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); $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); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $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; } $strSessionKey = fxBibBatchExecSessionKey($epr_id); if ($blnBatchReset) { $_SESSION[$strSessionKey] = []; fxBibSaveEpreuveBatchSort($epr_id, $strSort1, $strSort2); } elseif (!isset($_SESSION[$strSessionKey]) || !is_array($_SESSION[$strSessionKey])) { $_SESSION[$strSessionKey] = []; } $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); 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, $strSort1, $strSort2)); $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; } // MSIN-4445 — GO batch par paquets avec plage virtuelle (sans séquence en BD). if ($action == 'batch_apply_free_chunk') { $epr_id = intval($_POST['epr_id'] ?? 0); $intStart = (int)($_POST['start_bib'] ?? 0); $intEnd = (int)($_POST['end_bib'] ?? 0); list($strSort1, $strSort2) = fxBibParseBatchSortFromPost(); $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; } if ($epr_id <= 0 || $intStart <= 0 || $intEnd < $intStart) { echo json_encode([ 'success' => false, 'message' => fxBibMsg('bib_v4_global_batch_free_plan_invalid'), ]); exit; } // MSIN-4445 — Solo seulement ; une séquence existante n'empêche pas l'assignation libre. $intEveId = (int)($_POST['eve_id'] ?? 0); if ($intEveId > 0) { $checkEpr = fxBibAssertSoloEpreuveForGlobalBatch($epr_id, $intEveId); if (!$checkEpr['success']) { echo json_encode($checkEpr); exit; } } $strSessionKey = fxBibBatchExecSessionKey($epr_id) . '_free'; if ($blnBatchReset) { $_SESSION[$strSessionKey] = []; fxBibSaveEpreuveBatchSort($epr_id, $strSort1, $strSort2); } elseif (!isset($_SESSION[$strSessionKey]) || !is_array($_SESSION[$strSessionKey])) { $_SESSION[$strSessionKey] = []; } $participants = fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2); 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 = fxBibBuildVirtualRangesForPlan($intStart, $intEnd); $execChunk = fxProcessBatchAssignments($epr_id, $tabChunk, $tabRanges, 'execute'); $_SESSION[$strSessionKey] = array_merge($_SESSION[$strSessionKey], $execChunk); $intRemaining = count(fxGetParticipantsForBatchAssign($epr_id, $strSort1, $strSort2)); $intProcessed = max(0, $intTotalInitial - $intRemaining); $blnComplete = ($intRemaining === 0); 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); $html = renderBibView( $epr_id, 0, $strLangue, 'reset', $participants, fxBibTexte('bib_v4_ajax_title_reset_preview'), fxBibMsg('bib_v4_ajax_reset_preview_count', count($participants)), 'warning' ); echo json_encode([ 'success' => true, 'html' => $html ]); exit; } // ===================== // RESET APPLY // ===================== 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; } $participants = fxGetParticipantsForBatchReset($epr_id, $ranges); $count = 0; foreach ($participants as $p) { $par_id = intval($p['par_id']); if ($par_id <= 0) { continue; } $sql = " UPDATE resultats_participants SET no_bib = NULL, par_date_bib = NULL, par_maj = '" . fxGetDateTime() . "' WHERE par_id = $par_id AND is_cancelled = 0 "; $objDatabase->fxQuery($sql); $count++; } // MSIN-4453 — Effacer aussi no_equipe (sinon le team# original reste et est réutilisé) fxClearTeamNumbersAfterBibReset($epr_id, $participants); $html = renderBibView( $epr_id, 0, $strLangue, 'reset', $participants, fxBibTexte('bib_v4_ajax_title_reset_done'), fxBibMsg('bib_v4_ajax_reset_done_count', $count), 'success' ); echo json_encode([ 'success' => true, 'html' => $html ]); exit; } // ===================== // RESET ALL ÉPREUVE // ===================== 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; } $participants = fxGetParticipantsForEpreuveResetAll($epr_id); $count = 0; foreach ($participants as $p) { $par_id = intval($p['par_id']); if ($par_id <= 0) { continue; } $sql = " UPDATE resultats_participants SET no_bib = NULL, par_date_bib = NULL, par_maj = '" . fxGetDateTime() . "' WHERE par_id = $par_id AND is_cancelled = 0 "; $objDatabase->fxQuery($sql); $count++; } // MSIN-4453 — Effacer aussi no_equipe (aligné reset legacy) fxClearTeamNumbersAfterBibReset($epr_id); $html = renderBibView( $epr_id, 0, $strLangue, 'reset', $participants, fxBibTexte('bib_v4_ajax_title_reset_all_done'), fxBibMsg('bib_v4_ajax_reset_done_count', $count), 'success' ); echo json_encode([ 'success' => true, 'html' => $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); $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); $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-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; } // ===================== // ACTION INVALIDE // ===================== echo json_encode([ 'success' => false, 'message' => fxBibMsg('bib_v4_ajax_action_invalid') ]); exit;