MSIN-4520 — Implement functions for improved handling of Excel/CSV imports, including conversion of cell values to strings and building column indices based on headers. Update import logic to ensure accurate mapping of 'no_bib' values. Increment version code to 4.73.008 to reflect changes in import header matching and data processing.

This commit is contained in:
2026-08-14 09:14:56 -04:00
parent 1cd4220674
commit a7f1081d17
2 changed files with 121 additions and 21 deletions

View File

@ -267,6 +267,86 @@ function fxImportResultatsGuessField($strHeader) {
return $tabCandidates[0]['key'];
}
/**
* MSIN-4520 — cellule Excel/CSV → string (évite 1.0 / float vide).
*/
function fxImportResultatsCellToString($mix) {
if ($mix === null) {
return '';
}
if (is_bool($mix)) {
return $mix ? '1' : '0';
}
if (is_int($mix)) {
return (string)$mix;
}
if (is_float($mix)) {
if (is_nan($mix) || is_infinite($mix)) {
return '';
}
if (floor($mix) == $mix) {
return (string)intval($mix);
}
$str = rtrim(rtrim(sprintf('%.12F', $mix), '0'), '.');
return ($str === '-0') ? '0' : $str;
}
return trim((string)$mix);
}
/**
* MSIN-4520 — index colonnes : dabord par nom den-tête (profil), puis index, puis filet guess.
* Évite « Bib → Dossard » à lécran mais no_bib vide en BD (profil réappliqué au mauvais index).
*/
function fxImportResultatsBuildColIndex(array $tabColumns, array $tabHeaders, array $tabSavedHeaders = array()) {
$tabColIndex = array();
foreach ($tabColumns as $intIdx => $strKey) {
$strKey = (string)$strKey;
if ($strKey === '') {
continue;
}
$strSavedH = trim((string)($tabSavedHeaders[$intIdx] ?? ''));
if ($strSavedH === '') {
continue;
}
$strSavedNorm = fxImportResultatsNormalizeHeader($strSavedH);
foreach ($tabHeaders as $intHi => $strH) {
if (fxImportResultatsNormalizeHeader($strH) === $strSavedNorm) {
$tabColIndex[$strKey] = intval($intHi);
break;
}
}
}
foreach ($tabColumns as $intIdx => $strKey) {
$strKey = (string)$strKey;
if ($strKey === '' || isset($tabColIndex[$strKey])) {
continue;
}
$tabColIndex[$strKey] = intval($intIdx);
}
// Filet dossard : en-tête « Bib » / « Dossard » même si le mapping dindex a loupé
$intGuessBib = -1;
foreach ($tabHeaders as $intHi => $strH) {
if (fxImportResultatsGuessField($strH) === 'no_bib') {
$intGuessBib = intval($intHi);
break;
}
}
if ($intGuessBib >= 0) {
$intMapped = isset($tabColIndex['no_bib']) ? intval($tabColIndex['no_bib']) : -1;
$strMappedGuess = ($intMapped >= 0)
? fxImportResultatsGuessField((string)($tabHeaders[$intMapped] ?? ''))
: '';
if ($intMapped < 0 || $strMappedGuess !== 'no_bib') {
$tabColIndex['no_bib'] = $intGuessBib;
}
}
return $tabColIndex;
}
/**
* Clé de partage templates entre événements du même org (pas de com_id sur evenements).
* MSIN-4482 — stockée dans inscriptions_import_templates.com_id (legacy name = org_id).
@ -807,8 +887,8 @@ function fxImportResultatsSheetRawToRows(array $tabRaw) {
if (!is_array($tabRaw) || count($tabRaw) < 1) {
return array('state' => 'error', 'message' => 'Feuille Excel vide.');
}
$tabHeaders = array_map(function ($v) {
return trim((string)$v);
$tabHeaders = array_map(function ($v) {
return fxImportResultatsCellToString($v);
}, $tabRaw[0]);
while (count($tabHeaders) > 0 && end($tabHeaders) === '') {
array_pop($tabHeaders);
@ -836,9 +916,7 @@ function fxImportResultatsSheetRawToRows(array $tabRaw) {
if ($blnEmpty) {
continue;
}
$tabRows[] = array_map(function ($v) {
return trim((string)$v);
}, $tabRow);
$tabRows[] = array_map('fxImportResultatsCellToString', $tabRow);
}
return array('state' => 'ok', 'headers' => $tabHeaders, 'rows' => $tabRows);
}
@ -961,7 +1039,7 @@ function fxImportResultatsParseExcel($strPath, array $arrOptions = array()) {
if (!isset($tabWantNorm[$strName])) {
continue;
}
$tabRaw = $objSheet->toArray(null, true, true, false);
$tabRaw = $objSheet->toArray(null, true, false, false);
if (!is_array($tabRaw)) {
continue;
}
@ -997,7 +1075,7 @@ function fxImportResultatsParseExcel($strPath, array $arrOptions = array()) {
} else {
$objSheet = $objBook->getActiveSheet();
}
$tabRaw = $objSheet->toArray(null, true, true, false);
$tabRaw = $objSheet->toArray(null, true, false, false);
} catch (Exception $ex) {
return array('state' => 'error', 'message' => 'Lecture Excel échouée : ' . $ex->getMessage());
}
@ -1467,7 +1545,7 @@ function fxImportResultatsFindEventImportProfile($intEveId) {
if (!is_array($tabCheck) || empty($tabCheck[1]['ok'])) {
return null;
}
$sql = "SELECT tpl_id, eve_id, com_id, tpl_name, tpl_header_sig, tpl_mapping_json, tpl_maj"
$sql = "SELECT tpl_id, eve_id, com_id, tpl_name, tpl_header_sig, tpl_headers_json, tpl_mapping_json, tpl_maj"
. " FROM inscriptions_import_templates"
. " WHERE tpl_actif = 1 AND eve_id = " . $intEveId
. " ORDER BY tpl_maj DESC LIMIT 25";
@ -1481,6 +1559,10 @@ function fxImportResultatsFindEventImportProfile($intEveId) {
if (!is_array($arrMap)) {
$arrMap = array();
}
$tabSavedH = json_decode((string)($tab[$i]['tpl_headers_json'] ?? ''), true);
if (is_array($tabSavedH) && empty($arrMap['column_headers'])) {
$arrMap['column_headers'] = $tabSavedH;
}
$arrRow = array(
'tpl_id' => intval($tab[$i]['tpl_id']),
'eve_id' => intval($tab[$i]['eve_id']),
@ -2358,16 +2440,13 @@ function fxImportResultatsCommit($intEveId, $strToken, array $arrMapping) {
$tabPayValues = isset($arrMapping['pay_values']) && is_array($arrMapping['pay_values']) ? $arrMapping['pay_values'] : array();
$tabProValues = isset($arrMapping['pro_values']) && is_array($arrMapping['pro_values']) ? $arrMapping['pro_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);
$tabMapHeaders = array();
if (isset($arrMapping['column_headers']) && is_array($arrMapping['column_headers'])) {
$tabMapHeaders = $arrMapping['column_headers'];
}
$tabColIndex = fxImportResultatsBuildColIndex($tabColumns, $tabHeaders, $tabMapHeaders);
$tabQueIds = fxImportResultatsResolveQuestionIds($intEveId, $tabHeaders, $tabQuestionsMap);
$strNow = function_exists('fxGetDateTime') ? fxGetDateTime() : date('Y-m-d H:i:s');
$strOrigine = MSIN_IMPORT_RESULTATS_ORIGIN;
@ -2380,7 +2459,9 @@ function fxImportResultatsCommit($intEveId, $strToken, array $arrMapping) {
foreach ($tabRows as $intRowNum => $tabRow) {
$intLine = $intRowNum + 2; // +1 header Excel, +1 index 0
$strNamePreview = fxImportResultatsRowLabel($tabRow, $tabColIndex);
$strBibPreview = isset($tabColIndex['no_bib']) ? trim((string)($tabRow[$tabColIndex['no_bib']] ?? '')) : '';
$strBibPreview = isset($tabColIndex['no_bib'])
? fxImportResultatsCellToString($tabRow[$tabColIndex['no_bib']] ?? '')
: '';
$strSexePreview = '';
if (isset($tabColIndex['par_sexe'])) {
$strSexePreview = fxImportResultatsNormalizeSex(trim((string)($tabRow[$tabColIndex['par_sexe']] ?? '')));
@ -2459,7 +2540,7 @@ function fxImportResultatsCommit($intEveId, $strToken, array $arrMapping) {
if (!isset($tabColIndex[$strKey])) {
continue;
}
$strRaw = trim((string)($tabRow[$tabColIndex[$strKey]] ?? ''));
$strRaw = fxImportResultatsCellToString($tabRow[$tabColIndex[$strKey]] ?? '');
if ($strKey === 'par_sexe') {
$arrFields[$strKey] = fxImportResultatsNormalizeSex($strRaw);
} elseif ($strKey === 'par_naissance') {
@ -2717,6 +2798,7 @@ function fxImportResultatsCommit($intEveId, $strToken, array $arrMapping) {
. " pec_id = " . $intPecLink . ","
. " rol_id = 2,"
. " no_bib = '" . fxImportResultatsDbEsc($arrFields['no_bib']) . "',"
. " par_statut_course = 'CONF',"
. " par_external_id = '" . fxImportResultatsDbEsc($strExt) . "',"
. " par_origine = '" . fxImportResultatsDbEsc($strOrigine) . "'";
if ($objDatabase->fxQuery($sqlPar) === false) {
@ -3842,6 +3924,7 @@ function fxImportResultatsRenderEventPanel($intEveId) {
// MSIN-4520 — figer mode onglets + pays/province dans le profil événement
var mapping = {
columns: columns,
column_headers: (state.headers || []).slice(),
epr_values: epr_values,
pay_values: pay_values,
pro_values: pro_values,
@ -4032,9 +4115,26 @@ function fxImportResultatsRenderEventPanel($intEveId) {
function applyMappingObj(mapping) {
if (!mapping || !mapping.columns) return;
state.appliedMapping = mapping;
var savedHeaders = mapping.column_headers || mapping._headers || [];
var columns = mapping.columns || [];
$('#ms1_import_map_table tbody select.js-col-map').each(function (i) {
if (mapping.columns[i] != null) {
$(this).val(String(mapping.columns[i]));
var field = '';
var hNow = (state.headers && state.headers[i] != null) ? String(state.headers[i]) : '';
var hNowN = hNow.toLowerCase().replace(/\s+/g, ' ').trim();
if (savedHeaders.length && hNowN !== '') {
for (var j = 0; j < savedHeaders.length; j++) {
var hOld = String(savedHeaders[j] || '').toLowerCase().replace(/\s+/g, ' ').trim();
if (hOld === hNowN && columns[j]) {
field = String(columns[j]);
break;
}
}
}
if (!field && columns[i] != null) {
field = String(columns[i]);
}
if (field) {
$(this).val(field);
}
});
renderEprBox();

View File

@ -7,7 +7,7 @@
* Constantes *
*
**************/
define('_VERSION_CODE', '4.73.007'); // MSIN-4520 — reset import: delete par_id (pas LIMIT/affected_rows superadm)
define('_VERSION_CODE', '4.73.008'); // MSIN-4520 — import dossard: match en-tête Bib + valeurs Excel brutes
define('_DATE_CODE', '2026-08-14');
//MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');