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;