From 8929236e850179b702efe3215780714eb7235f3c Mon Sep 17 00:00:00 2001 From: stephan Date: Fri, 24 Jul 2026 16:16:00 -0400 Subject: [PATCH] Enhance participant import functionality in MSIN-4482 by updating the import phases and adding new fields for emergency contact details. Implement commit action in AJAX handling for result mapping validation. Increment version code. --- .../projet/10-hors-transaction-import.mdc | 11 +- php/inc_fx_import_resultats.php | 492 +++++++++++++++++- superadm/ajax_import_resultats.php | 17 + 3 files changed, 512 insertions(+), 8 deletions(-) diff --git a/.cursor/rules/projet/10-hors-transaction-import.mdc b/.cursor/rules/projet/10-hors-transaction-import.mdc index 5814f8a..243eb76 100644 --- a/.cursor/rules/projet/10-hors-transaction-import.mdc +++ b/.cursor/rules/projet/10-hors-transaction-import.mdc @@ -42,11 +42,12 @@ Aussi utile plus tard le **jour J** sur une course **avec** inscription en ligne ## Phases (réalignées) -1. **UI import superadm** (en cours) — dropzone, mapping colonnes + épreuves, templates persistants -2. **Écriture `resultats_*`** — upsert clé unique + questions C (sans panier) -3. **ChronoTrack** — push avec `par_external_id` -4. **Saisie / édition** — fiche rapide (+ jour J) -5. **Hub minimisé** — seulement si/quand nécessaire +1. **UI import superadm** — dropzone, mapping colonnes + épreuves, templates, bouton **Importer** +2. **Écriture `resultats_*`** — upsert clé unique + questions C (sans panier) — en place +3. **Mapping pays** (valeurs fichier → `pay_id`) — **étape 2** +4. **ChronoTrack** — push avec `par_external_id` +5. **Saisie / édition** — fiche rapide (+ jour J) +6. **Hub minimisé** — seulement si/quand nécessaire ## UI import (décisions) diff --git a/php/inc_fx_import_resultats.php b/php/inc_fx_import_resultats.php index 2232fde..67e7c3a 100644 --- a/php/inc_fx_import_resultats.php +++ b/php/inc_fx_import_resultats.php @@ -23,6 +23,8 @@ function fxImportResultatsTargetFields() { array('key' => 'par_age', 'label' => 'Âge', 'group' => 'Identité'), array('key' => 'par_courriel', 'label' => 'Courriel', 'group' => 'Contact'), array('key' => 'par_telephone1', 'label' => 'Téléphone', 'group' => 'Contact'), + array('key' => 'par_contact_urgence_nom', 'label' => 'Contact urgence — nom', 'group' => 'Contact'), + array('key' => 'par_contact_urgence_telephone', 'label' => 'Contact urgence — téléphone', 'group' => 'Contact'), array('key' => 'par_adresse', 'label' => 'Adresse', 'group' => 'Contact'), array('key' => 'par_ville', 'label' => 'Ville', 'group' => 'Contact'), array('key' => 'par_codepostal', 'label' => 'Code postal', 'group' => 'Contact'), @@ -83,6 +85,8 @@ function fxImportResultatsGuessField($strHeader) { 'par_age' => array('age', 'âge'), 'par_courriel' => array('courriel', 'e-mail', 'email', 'mail'), 'par_telephone1' => array('telephone', 'téléphone', 'phone', 'mobile', 'cell', 'tel'), + 'par_contact_urgence_nom' => array('emergency contact name', 'contact urgence', 'nom en cas d\'urgence', 'urgence nom'), + 'par_contact_urgence_telephone' => array('emergency contact number', 'emergency contact phone', 'numéro du contact en cas d\'urgence', 'urgence tel'), 'par_adresse' => array('adresse', 'address', 'street'), 'par_ville' => array('ville', 'city', 'town'), 'par_codepostal' => array('code postal', 'postal', 'zipcode', 'zip'), @@ -662,10 +666,451 @@ function fxImportResultatsValidateMapping($intEveId, $strToken, array $arrMappin return array( 'state' => 'ok', - 'message' => 'Mapping prêt — ' . count($arrParsed['rows']) . ' ligne(s). L\'écriture dans resultats_* arrive juste après cette interface.', + 'message' => 'Mapping prêt — ' . count($arrParsed['rows']) . ' ligne(s). Vous pouvez importer.', 'warnings' => $tabWarn, 'row_count' => count($arrParsed['rows']), - 'commit_ready' => false, + 'commit_ready' => true, + ); +} + +function fxImportResultatsDbEsc($str) { + global $objDatabase; + if (isset($objDatabase) && is_object($objDatabase) && method_exists($objDatabase, 'fxEscape')) { + return $objDatabase->fxEscape((string)$str); + } + return addslashes((string)$str); +} + +/** MSIN-4482 — Male/Female/Other → M/F/N (aligné CT). */ +function fxImportResultatsNormalizeSex($str) { + $str = trim((string)$str); + $strLow = mb_strtolower($str, 'UTF-8'); + if ($strLow === 'f' || $strLow === 'female' || $strLow === 'féminin' || $strLow === 'feminin' || $strLow === 'femme') { + return 'F'; + } + if ($strLow === 'm' || $strLow === 'h' || $strLow === 'male' || $strLow === 'masculin' || $strLow === 'homme') { + return 'M'; + } + if ($strLow === 'other' || $strLow === 'autre' || $strLow === 'n' || $strLow === 'x') { + return 'N'; + } + if ($str === 'F' || $str === 'M' || $str === 'N' || $str === 'H') { + return ($str === 'H') ? 'M' : $str; + } + return ''; +} + +/** MSIN-4482 — parse dates type 5/24/94 → Y-m-d. */ +function fxImportResultatsNormalizeBirthdate($str) { + $str = trim((string)$str); + if ($str === '' || $str === '0000-00-00') { + return ''; + } + if (preg_match('/^(\d{4})-(\d{2})-(\d{2})/', $str, $m)) { + return $m[1] . '-' . $m[2] . '-' . $m[3]; + } + // m/d/y ou m/d/yyyy + if (preg_match('/^(\d{1,2})[\/\-.](\d{1,2})[\/\-.](\d{2,4})$/', $str, $m)) { + $intM = intval($m[1]); + $intD = intval($m[2]); + $intY = intval($m[3]); + if ($intY < 100) { + $intY += ($intY >= 70) ? 1900 : 2000; + } + if (checkdate($intM, $intD, $intY)) { + return sprintf('%04d-%02d-%02d', $intY, $intM, $intD); + } + // essai j/m/a + if (checkdate($intD, $intM, $intY)) { + return sprintf('%04d-%02d-%02d', $intY, $intD, $intM); + } + } + $intTs = strtotime($str); + if ($intTs !== false) { + return date('Y-m-d', $intTs); + } + return ''; +} + +/** + * Résout que_id pour colonnes questions (match existant ou création confirmée). + * @return array colIndex => que_id + */ +function fxImportResultatsResolveQuestionIds($intEveId, array $tabHeaders, array $tabQuestionsMap) { + global $objDatabase; + + $intEveId = intval($intEveId); + $tabOut = array(); + foreach ($tabQuestionsMap as $strCol => $strQue) { + $intCol = intval($strCol); + $strQue = trim((string)$strQue); + if ($strQue === '' || $strQue === '0') { + continue; + } + if ($strQue === 'new') { + $strLabel = trim((string)($tabHeaders[$intCol] ?? ('Question ' . ($intCol + 1)))); + if (mb_strlen($strLabel, 'UTF-8') > 100) { + $strLabel = mb_substr($strLabel, 0, 97, 'UTF-8') . '...'; + } + // Réutiliser si même libellé déjà créé sur l'événement + $sqlFind = "SELECT que_id FROM inscriptions_questions" + . " WHERE eve_id = " . $intEveId + . " AND que_question_fr = '" . fxImportResultatsDbEsc($strLabel) . "'" + . " LIMIT 1"; + $tabFind = $objDatabase->fxGetResults($sqlFind); + if (is_array($tabFind) && !empty($tabFind[1]['que_id'])) { + $tabOut[(string)$intCol] = intval($tabFind[1]['que_id']); + continue; + } + $sqlIns = "INSERT INTO inscriptions_questions SET" + . " eve_id = " . $intEveId . "," + . " que_question_fr = '" . fxImportResultatsDbEsc($strLabel) . "'," + . " que_question_en = '" . fxImportResultatsDbEsc($strLabel) . "'," + . " que_choix_fr = ''," + . " que_choix_en = ''," + . " que_liste = 0," + . " que_epreuves_incluses = ''," + . " que_required = 0," + . " que_tri = 900," + . " que_participant_1 = 1," + . " que_modifiable = 1," + . " que_actif = 1," + . " que_hidden = 0"; + if ($objDatabase->fxQuery($sqlIns) === false) { + continue; + } + $tabOut[(string)$intCol] = intval($objDatabase->fxGetLastId()); + continue; + } + $intQueId = intval($strQue); + if ($intQueId > 0) { + $tabOut[(string)$intCol] = $intQueId; + } + } + return $tabOut; +} + +function fxImportResultatsSaveQuestionAnswer($intParIdOriginal, $intPecId, $intQueId, $strHeader, $strValue) { + global $objDatabase; + + $intParIdOriginal = intval($intParIdOriginal); + $intPecId = intval($intPecId); + $intQueId = intval($intQueId); + $strValue = trim((string)$strValue); + $strHeader = trim((string)$strHeader); + if ($intQueId <= 0 || $intPecId <= 0) { + return; + } + $strNow = function_exists('fxGetDateTime') ? fxGetDateTime() : date('Y-m-d H:i:s'); + $sqlFind = "SELECT pqu_id FROM resultats_questions" + . " WHERE pec_id = " . $intPecId + . " AND que_id = " . $intQueId + . " AND par_id = " . $intParIdOriginal + . " LIMIT 1"; + $tabFind = $objDatabase->fxGetResults($sqlFind); + if (is_array($tabFind) && !empty($tabFind[1]['pqu_id'])) { + $sql = "UPDATE resultats_questions SET" + . " que_choix_fr = '" . fxImportResultatsDbEsc($strValue) . "'," + . " que_choix_en = '" . fxImportResultatsDbEsc($strValue) . "'," + . " pqu_maj = '" . fxImportResultatsDbEsc($strNow) . "'" + . " WHERE pqu_id = " . intval($tabFind[1]['pqu_id']); + $objDatabase->fxQuery($sql); + return; + } + $sql = "INSERT INTO resultats_questions SET" + . " par_id = " . $intParIdOriginal . "," + . " pec_id = " . $intPecId . "," + . " que_id = " . $intQueId . "," + . " que_question_fr = '" . fxImportResultatsDbEsc($strHeader) . "'," + . " que_question_en = '" . fxImportResultatsDbEsc($strHeader) . "'," + . " que_choix_fr = '" . fxImportResultatsDbEsc($strValue) . "'," + . " que_choix_en = '" . fxImportResultatsDbEsc($strValue) . "'," + . " que_modifiable = 1," + . " pqu_note = ''," + . " pqu_maj = '" . fxImportResultatsDbEsc($strNow) . "'," + . " no_panier = ''," + . " no_commande = ''," + . " pqu_id_original = 0"; + $objDatabase->fxQuery($sql); +} + +/** + * MSIN-4482 — écriture resultats_* (sans panier), upsert sur par_external_id. + */ +function fxImportResultatsCommit($intEveId, $strToken, array $arrMapping) { + global $objDatabase; + + @set_time_limit(300); + + $arrVal = fxImportResultatsValidateMapping($intEveId, $strToken, $arrMapping); + if (($arrVal['state'] ?? '') !== 'ok') { + return $arrVal; + } + + $intEveId = intval($intEveId); + $strPath = fxImportResultatsResolveTokenPath($intEveId, $strToken); + $arrParsed = fxImportResultatsParseFile($strPath); + if (($arrParsed['state'] ?? '') !== 'ok') { + return $arrParsed; + } + + // Colonnes requises pour l'import + $sqlCol = "SELECT COUNT(*) AS n FROM information_schema.COLUMNS" + . " WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'resultats_participants'" + . " AND COLUMN_NAME = 'par_external_id'"; + $tabCol = $objDatabase->fxGetResults($sqlCol); + if (!is_array($tabCol) || intval($tabCol[1]['n'] ?? 0) < 1) { + return array( + 'state' => 'error', + 'message' => 'Colonnes import absentes — exécuter sql/MSIN-4482-import-resultats-templates.sql en dev préprod.', + ); + } + + $tabHeaders = $arrParsed['headers']; + $tabRows = $arrParsed['rows']; + $tabColumns = isset($arrMapping['columns']) && is_array($arrMapping['columns']) ? $arrMapping['columns'] : array(); + $tabEprValues = isset($arrMapping['epr_values']) && is_array($arrMapping['epr_values']) ? $arrMapping['epr_values'] : array(); + $tabQuestionsMap = isset($arrMapping['questions']) && is_array($arrMapping['questions']) ? $arrMapping['questions'] : array(); + + $tabColIndex = array(); + foreach ($tabColumns as $intIdx => $strKey) { + $strKey = (string)$strKey; + if ($strKey === '') { + continue; + } + $tabColIndex[$strKey] = intval($intIdx); + } + + $tabQueIds = fxImportResultatsResolveQuestionIds($intEveId, $tabHeaders, $tabQuestionsMap); + $strNow = function_exists('fxGetDateTime') ? fxGetDateTime() : date('Y-m-d H:i:s'); + $strOrigine = MSIN_IMPORT_RESULTATS_ORIGIN; + + $intCreated = 0; + $intUpdated = 0; + $intSkipped = 0; + $tabRowErrors = array(); + + foreach ($tabRows as $intRowNum => $tabRow) { + $strExt = ''; + if (isset($tabColIndex['par_external_id'])) { + $strExt = trim((string)($tabRow[$tabColIndex['par_external_id']] ?? '')); + } + if ($strExt === '') { + $intSkipped++; + if (count($tabRowErrors) < 15) { + $tabRowErrors[] = 'Ligne ' . ($intRowNum + 2) . ' : clé unique vide'; + } + continue; + } + + $strEprRaw = ''; + if (isset($tabColIndex['epr'])) { + $strEprRaw = trim((string)($tabRow[$tabColIndex['epr']] ?? '')); + } + $intEprId = intval($tabEprValues[$strEprRaw] ?? 0); + if ($intEprId <= 0) { + $intSkipped++; + if (count($tabRowErrors) < 15) { + $tabRowErrors[] = 'Ligne ' . ($intRowNum + 2) . ' : épreuve non mappée (« ' . $strEprRaw . ' »)'; + } + continue; + } + + $arrFields = array( + 'par_prenom' => '', + 'par_nom' => '', + 'par_sexe' => '', + 'par_naissance' => '', + 'par_age' => 0, + 'par_courriel' => '', + 'par_telephone1' => '', + 'par_adresse' => '', + 'par_ville' => '', + 'par_codepostal' => '', + 'no_bib' => '', + 'par_nom_equipe' => '', + 'par_no_equipe' => 0, + 'par_contact_urgence_nom' => '', + 'par_contact_urgence_telephone' => '', + ); + foreach ($arrFields as $strKey => $mixDefault) { + if (!isset($tabColIndex[$strKey])) { + continue; + } + $strRaw = trim((string)($tabRow[$tabColIndex[$strKey]] ?? '')); + if ($strKey === 'par_sexe') { + $arrFields[$strKey] = fxImportResultatsNormalizeSex($strRaw); + } elseif ($strKey === 'par_naissance') { + $arrFields[$strKey] = fxImportResultatsNormalizeBirthdate($strRaw); + } elseif ($strKey === 'par_age' || $strKey === 'par_no_equipe') { + $arrFields[$strKey] = intval($strRaw); + } else { + $arrFields[$strKey] = $strRaw; + } + } + if (intval($arrFields['par_age']) <= 0 && $arrFields['par_naissance'] !== '') { + $intTs = strtotime($arrFields['par_naissance']); + if ($intTs !== false) { + $arrFields['par_age'] = intval(floor((time() - $intTs) / 31557600)); + } + } + + // Upsert participant import + $sqlExist = "SELECT par_id, pec_id, par_id_original FROM resultats_participants" + . " WHERE eve_id = " . $intEveId + . " AND par_external_id = '" . fxImportResultatsDbEsc($strExt) . "'" + . " LIMIT 1"; + $tabExist = $objDatabase->fxGetResults($sqlExist); + $blnUpdate = (is_array($tabExist) && !empty($tabExist[1]['par_id'])); + + if ($blnUpdate) { + $intParId = intval($tabExist[1]['par_id']); + $intPecId = intval($tabExist[1]['pec_id']); + $intParOrig = intval($tabExist[1]['par_id_original']); + if ($intParOrig <= 0) { + $intParOrig = $intParId; + } + + if ($intPecId > 0) { + $sqlPec = "UPDATE resultats_epreuves_commandees SET" + . " epr_id = " . $intEprId . "," + . " pec_maj = '" . fxImportResultatsDbEsc($strNow) . "'" + . " WHERE pec_id = " . $intPecId; + $objDatabase->fxQuery($sqlPec); + } + + $sqlUp = "UPDATE resultats_participants SET" + . " epr_id = " . $intEprId . "," + . " par_prenom = '" . fxImportResultatsDbEsc($arrFields['par_prenom']) . "'," + . " par_nom = '" . fxImportResultatsDbEsc($arrFields['par_nom']) . "'," + . " par_sexe = '" . fxImportResultatsDbEsc($arrFields['par_sexe']) . "'," + . " par_naissance = " . ($arrFields['par_naissance'] !== '' ? ("'" . fxImportResultatsDbEsc($arrFields['par_naissance']) . "'") : "NULL") . "," + . " par_age = " . intval($arrFields['par_age']) . "," + . " par_courriel = '" . fxImportResultatsDbEsc($arrFields['par_courriel']) . "'," + . " par_telephone1 = '" . fxImportResultatsDbEsc($arrFields['par_telephone1']) . "'," + . " par_adresse = '" . fxImportResultatsDbEsc($arrFields['par_adresse']) . "'," + . " par_ville = '" . fxImportResultatsDbEsc($arrFields['par_ville']) . "'," + . " par_codepostal = '" . fxImportResultatsDbEsc($arrFields['par_codepostal']) . "'," + . " no_bib = '" . fxImportResultatsDbEsc($arrFields['no_bib']) . "'," + . " par_nom_equipe = '" . fxImportResultatsDbEsc($arrFields['par_nom_equipe']) . "'," + . " par_no_equipe = " . intval($arrFields['par_no_equipe']) . "," + . " par_origine = '" . fxImportResultatsDbEsc($strOrigine) . "'," + . " par_maj = '" . fxImportResultatsDbEsc($strNow) . "'" + . " WHERE par_id = " . $intParId; + if ($objDatabase->fxQuery($sqlUp) === false) { + $intSkipped++; + if (count($tabRowErrors) < 15) { + $tabRowErrors[] = 'Ligne ' . ($intRowNum + 2) . ' : échec UPDATE'; + } + continue; + } + // urgence si colonnes présentes (ignorer erreur silencieuse) + $sqlUrg = "UPDATE resultats_participants SET" + . " par_contact_urgence_nom = '" . fxImportResultatsDbEsc($arrFields['par_contact_urgence_nom']) . "'," + . " par_contact_urgence_telephone = '" . fxImportResultatsDbEsc($arrFields['par_contact_urgence_telephone']) . "'" + . " WHERE par_id = " . $intParId; + @$objDatabase->fxQuery($sqlUrg); + + $intUpdated++; + } else { + $sqlPec = "INSERT INTO resultats_epreuves_commandees SET" + . " eve_id = " . $intEveId . "," + . " epr_id = " . $intEprId . "," + . " no_panier = ''," + . " pec_label = ''," + . " pec_nom_equipe = '" . fxImportResultatsDbEsc($arrFields['par_nom_equipe']) . "'," + . " pec_equipe = 0," + . " com_id = 0," + . " pec_maj = '" . fxImportResultatsDbEsc($strNow) . "'," + . " pec_id_original = 0," + . " no_commande = ''," + . " is_cancelled = 0"; + if ($objDatabase->fxQuery($sqlPec) === false) { + $intSkipped++; + if (count($tabRowErrors) < 15) { + $tabRowErrors[] = 'Ligne ' . ($intRowNum + 2) . ' : échec INSERT pec'; + } + continue; + } + $intPecId = intval($objDatabase->fxGetLastId()); + $objDatabase->fxQuery( + "UPDATE resultats_epreuves_commandees SET pec_id_original = " . $intPecId + . " WHERE pec_id = " . $intPecId + ); + + $sqlPar = "INSERT INTO resultats_participants SET" + . " eve_id = " . $intEveId . "," + . " epr_id = " . $intEprId . "," + . " par_prenom = '" . fxImportResultatsDbEsc($arrFields['par_prenom']) . "'," + . " par_nom = '" . fxImportResultatsDbEsc($arrFields['par_nom']) . "'," + . " par_naissance = " . ($arrFields['par_naissance'] !== '' ? ("'" . fxImportResultatsDbEsc($arrFields['par_naissance']) . "'") : "NULL") . "," + . " par_age = " . intval($arrFields['par_age']) . "," + . " par_sexe = '" . fxImportResultatsDbEsc($arrFields['par_sexe']) . "'," + . " par_adresse = '" . fxImportResultatsDbEsc($arrFields['par_adresse']) . "'," + . " par_ville = '" . fxImportResultatsDbEsc($arrFields['par_ville']) . "'," + . " pro_id = 0," + . " par_codepostal = '" . fxImportResultatsDbEsc($arrFields['par_codepostal']) . "'," + . " pay_id = 0," + . " par_telephone1 = '" . fxImportResultatsDbEsc($arrFields['par_telephone1']) . "'," + . " par_courriel = '" . fxImportResultatsDbEsc($arrFields['par_courriel']) . "'," + . " com_id = 0," + . " par_nom_equipe = '" . fxImportResultatsDbEsc($arrFields['par_nom_equipe']) . "'," + . " no_panier = ''," + . " par_maj = '" . fxImportResultatsDbEsc($strNow) . "'," + . " no_commande = ''," + . " par_id_original = 0," + . " is_cancelled = 0," + . " par_no_equipe = " . intval($arrFields['par_no_equipe']) . "," + . " par_equipe = 0," + . " pec_id = " . $intPecId . "," + . " rol_id = 2," + . " no_bib = '" . fxImportResultatsDbEsc($arrFields['no_bib']) . "'," + . " par_external_id = '" . fxImportResultatsDbEsc($strExt) . "'," + . " par_origine = '" . fxImportResultatsDbEsc($strOrigine) . "'"; + if ($objDatabase->fxQuery($sqlPar) === false) { + $intSkipped++; + if (count($tabRowErrors) < 15) { + $tabRowErrors[] = 'Ligne ' . ($intRowNum + 2) . ' : échec INSERT participant'; + } + continue; + } + $intParId = intval($objDatabase->fxGetLastId()); + $intParOrig = $intParId; + $objDatabase->fxQuery( + "UPDATE resultats_participants SET par_id_original = " . $intParId + . " WHERE par_id = " . $intParId + ); + $sqlUrg = "UPDATE resultats_participants SET" + . " par_contact_urgence_nom = '" . fxImportResultatsDbEsc($arrFields['par_contact_urgence_nom']) . "'," + . " par_contact_urgence_telephone = '" . fxImportResultatsDbEsc($arrFields['par_contact_urgence_telephone']) . "'" + . " WHERE par_id = " . $intParId; + @$objDatabase->fxQuery($sqlUrg); + + $intCreated++; + } + + foreach ($tabQueIds as $strCol => $intQueId) { + $intCol = intval($strCol); + $strVal = trim((string)($tabRow[$intCol] ?? '')); + $strH = (string)($tabHeaders[$intCol] ?? ''); + fxImportResultatsSaveQuestionAnswer($intParOrig, $intPecId, $intQueId, $strH, $strVal); + } + } + + $strMsg = 'Import terminé : ' . $intCreated . ' créé(s), ' . $intUpdated . ' mis à jour, ' + . $intSkipped . ' ignoré(s) sur ' . count($tabRows) . ' ligne(s).'; + if (count($tabRowErrors) > 0) { + $strMsg .= ' Exemples : ' . implode(' · ', $tabRowErrors); + } + + return array( + 'state' => 'ok', + 'message' => $strMsg, + 'created' => $intCreated, + 'updated' => $intUpdated, + 'skipped' => $intSkipped, + 'row_count' => count($tabRows), + 'errors_sample' => $tabRowErrors, ); } @@ -779,9 +1224,12 @@ function fxImportResultatsRenderEventPanel($intEveId) { - + @@ -1132,6 +1580,44 @@ function fxImportResultatsRenderEventPanel($intEveId) { showStatus('danger', 'Erreur réseau (validation).'); }); }); + + $('#ms1_import_commit').on('click', function () { + if (!state || !state.token) return; + var mapping = collectMapping(); + if (!window.confirm('Importer / mettre à jour les participants dans resultats_* pour cet événement ?\n(Sans panier ni paiement — upsert sur la clé unique.)')) { + return; + } + var $btn = $(this); + $btn.prop('disabled', true); + showStatus('info', 'Import en cours — cela peut prendre une minute pour un gros fichier…'); + $.ajax({ + url: ajaxUrl, + method: 'POST', + dataType: 'text', + data: { + a: 'commit', + eve_id: eveId, + token: state.token, + mapping: JSON.stringify(mapping) + } + }).done(function (raw) { + $btn.prop('disabled', false); + var res = parseAjaxJson(raw); + if (!res || res.state !== 'ok') { + showStatus('danger', (res && res.message) ? res.message : 'Échec import'); + return; + } + showStatus('success', res.message || 'Import terminé.'); + }).fail(function (xhr) { + $btn.prop('disabled', false); + var res = parseAjaxJson(xhr && xhr.responseText); + if (res && res.state === 'ok') { + showStatus('success', res.message || 'Import terminé.'); + return; + } + showStatus('danger', 'Erreur réseau (import). Timeout possible sur très gros fichiers — réessayez ou coupez le fichier.'); + }); + }); })(window.jQuery); 'error', 'message' => 'eve_id manquant'); + break; + } + $strToken = trim((string)($_REQUEST['token'] ?? '')); + $arrMapping = $_REQUEST['mapping'] ?? array(); + if (is_string($arrMapping)) { + $arrDecoded = json_decode($arrMapping, true); + $arrMapping = is_array($arrDecoded) ? $arrDecoded : array(); + } + if (!is_array($arrMapping)) { + $arrMapping = array(); + } + $tabRetour = fxImportResultatsCommit($intEveId, $strToken, $arrMapping); + break; + default: $tabRetour = array('state' => 'error', 'message' => 'action_invalide'); break;