980 lines
30 KiB
PHP
980 lines
30 KiB
PHP
<?php
|
||
/**
|
||
* MSIN-4574 — Payload MS1 → ChronoTrack (source de vérité écriture entry).
|
||
* Mapping pays / bib / sexe / notes / BuildEntryPayload.
|
||
* Ne contient pas le push HTTP ni la résolution des conflits bib côté CT.
|
||
*/
|
||
|
||
/**
|
||
* MSIN-4574 — table ISO-3166 alpha-2 → alpha-3 (alignée sql/MSIN-inscriptions-pays-iso3.sql).
|
||
* Enrichie au runtime avec pay_iso3 en BD si la colonne existe.
|
||
*/
|
||
function fxChronotrackApiSyncIso2ToIso3Map() {
|
||
static $tabMap = null;
|
||
if ($tabMap !== null) {
|
||
return $tabMap;
|
||
}
|
||
$tabMap = array(
|
||
'AD' => 'AND',
|
||
'AE' => 'ARE',
|
||
'AF' => 'AFG',
|
||
'AG' => 'ATG',
|
||
'AI' => 'AIA',
|
||
'AL' => 'ALB',
|
||
'AM' => 'ARM',
|
||
'AO' => 'AGO',
|
||
'AQ' => 'ATA',
|
||
'AR' => 'ARG',
|
||
'AS' => 'ASM',
|
||
'AT' => 'AUT',
|
||
'AU' => 'AUS',
|
||
'AW' => 'ABW',
|
||
'AX' => 'ALA',
|
||
'AZ' => 'AZE',
|
||
'BA' => 'BIH',
|
||
'BB' => 'BRB',
|
||
'BD' => 'BGD',
|
||
'BE' => 'BEL',
|
||
'BF' => 'BFA',
|
||
'BG' => 'BGR',
|
||
'BH' => 'BHR',
|
||
'BI' => 'BDI',
|
||
'BJ' => 'BEN',
|
||
'BL' => 'BLM',
|
||
'BM' => 'BMU',
|
||
'BN' => 'BRN',
|
||
'BO' => 'BOL',
|
||
'BQ' => 'BES',
|
||
'BR' => 'BRA',
|
||
'BS' => 'BHS',
|
||
'BT' => 'BTN',
|
||
'BV' => 'BVT',
|
||
'BW' => 'BWA',
|
||
'BY' => 'BLR',
|
||
'BZ' => 'BLZ',
|
||
'CA' => 'CAN',
|
||
'CC' => 'CCK',
|
||
'CD' => 'COD',
|
||
'CF' => 'CAF',
|
||
'CG' => 'COG',
|
||
'CH' => 'CHE',
|
||
'CI' => 'CIV',
|
||
'CK' => 'COK',
|
||
'CL' => 'CHL',
|
||
'CM' => 'CMR',
|
||
'CN' => 'CHN',
|
||
'CO' => 'COL',
|
||
'CR' => 'CRI',
|
||
'CU' => 'CUB',
|
||
'CV' => 'CPV',
|
||
'CW' => 'CUW',
|
||
'CX' => 'CXR',
|
||
'CY' => 'CYP',
|
||
'CZ' => 'CZE',
|
||
'DE' => 'DEU',
|
||
'DJ' => 'DJI',
|
||
'DK' => 'DNK',
|
||
'DM' => 'DMA',
|
||
'DO' => 'DOM',
|
||
'DZ' => 'DZA',
|
||
'EC' => 'ECU',
|
||
'EE' => 'EST',
|
||
'EG' => 'EGY',
|
||
'EH' => 'ESH',
|
||
'ER' => 'ERI',
|
||
'ES' => 'ESP',
|
||
'ET' => 'ETH',
|
||
'FI' => 'FIN',
|
||
'FJ' => 'FJI',
|
||
'FK' => 'FLK',
|
||
'FM' => 'FSM',
|
||
'FO' => 'FRO',
|
||
'FR' => 'FRA',
|
||
'GA' => 'GAB',
|
||
'GB' => 'GBR',
|
||
'GD' => 'GRD',
|
||
'GE' => 'GEO',
|
||
'GF' => 'GUF',
|
||
'GG' => 'GGY',
|
||
'GH' => 'GHA',
|
||
'GI' => 'GIB',
|
||
'GL' => 'GRL',
|
||
'GM' => 'GMB',
|
||
'GN' => 'GIN',
|
||
'GP' => 'GLP',
|
||
'GQ' => 'GNQ',
|
||
'GR' => 'GRC',
|
||
'GS' => 'SGS',
|
||
'GT' => 'GTM',
|
||
'GU' => 'GUM',
|
||
'GW' => 'GNB',
|
||
'GY' => 'GUY',
|
||
'HK' => 'HKG',
|
||
'HM' => 'HMD',
|
||
'HN' => 'HND',
|
||
'HR' => 'HRV',
|
||
'HT' => 'HTI',
|
||
'HU' => 'HUN',
|
||
'ID' => 'IDN',
|
||
'IE' => 'IRL',
|
||
'IL' => 'ISR',
|
||
'IM' => 'IMN',
|
||
'IN' => 'IND',
|
||
'IO' => 'IOT',
|
||
'IQ' => 'IRQ',
|
||
'IR' => 'IRN',
|
||
'IS' => 'ISL',
|
||
'IT' => 'ITA',
|
||
'JE' => 'JEY',
|
||
'JM' => 'JAM',
|
||
'JO' => 'JOR',
|
||
'JP' => 'JPN',
|
||
'KE' => 'KEN',
|
||
'KG' => 'KGZ',
|
||
'KH' => 'KHM',
|
||
'KI' => 'KIR',
|
||
'KM' => 'COM',
|
||
'KN' => 'KNA',
|
||
'KP' => 'PRK',
|
||
'KR' => 'KOR',
|
||
'KW' => 'KWT',
|
||
'KY' => 'CYM',
|
||
'KZ' => 'KAZ',
|
||
'LA' => 'LAO',
|
||
'LB' => 'LBN',
|
||
'LC' => 'LCA',
|
||
'LI' => 'LIE',
|
||
'LK' => 'LKA',
|
||
'LR' => 'LBR',
|
||
'LS' => 'LSO',
|
||
'LT' => 'LTU',
|
||
'LU' => 'LUX',
|
||
'LV' => 'LVA',
|
||
'LY' => 'LBY',
|
||
'MA' => 'MAR',
|
||
'MC' => 'MCO',
|
||
'MD' => 'MDA',
|
||
'ME' => 'MNE',
|
||
'MF' => 'MAF',
|
||
'MG' => 'MDG',
|
||
'MH' => 'MHL',
|
||
'MK' => 'MKD',
|
||
'ML' => 'MLI',
|
||
'MM' => 'MMR',
|
||
'MN' => 'MNG',
|
||
'MO' => 'MAC',
|
||
'MP' => 'MNP',
|
||
'MQ' => 'MTQ',
|
||
'MR' => 'MRT',
|
||
'MS' => 'MSR',
|
||
'MT' => 'MLT',
|
||
'MU' => 'MUS',
|
||
'MV' => 'MDV',
|
||
'MW' => 'MWI',
|
||
'MX' => 'MEX',
|
||
'MY' => 'MYS',
|
||
'MZ' => 'MOZ',
|
||
'NA' => 'NAM',
|
||
'NC' => 'NCL',
|
||
'NE' => 'NER',
|
||
'NF' => 'NFK',
|
||
'NG' => 'NGA',
|
||
'NI' => 'NIC',
|
||
'NL' => 'NLD',
|
||
'NO' => 'NOR',
|
||
'NP' => 'NPL',
|
||
'NR' => 'NRU',
|
||
'NU' => 'NIU',
|
||
'NZ' => 'NZL',
|
||
'OM' => 'OMN',
|
||
'PA' => 'PAN',
|
||
'PE' => 'PER',
|
||
'PF' => 'PYF',
|
||
'PG' => 'PNG',
|
||
'PH' => 'PHL',
|
||
'PK' => 'PAK',
|
||
'PL' => 'POL',
|
||
'PM' => 'SPM',
|
||
'PN' => 'PCN',
|
||
'PR' => 'PRI',
|
||
'PS' => 'PSE',
|
||
'PT' => 'PRT',
|
||
'PW' => 'PLW',
|
||
'PY' => 'PRY',
|
||
'QA' => 'QAT',
|
||
'RE' => 'REU',
|
||
'RO' => 'ROU',
|
||
'RS' => 'SRB',
|
||
'RU' => 'RUS',
|
||
'RW' => 'RWA',
|
||
'SA' => 'SAU',
|
||
'SB' => 'SLB',
|
||
'SC' => 'SYC',
|
||
'SD' => 'SDN',
|
||
'SE' => 'SWE',
|
||
'SG' => 'SGP',
|
||
'SH' => 'SHN',
|
||
'SI' => 'SVN',
|
||
'SJ' => 'SJM',
|
||
'SK' => 'SVK',
|
||
'SL' => 'SLE',
|
||
'SM' => 'SMR',
|
||
'SN' => 'SEN',
|
||
'SO' => 'SOM',
|
||
'SR' => 'SUR',
|
||
'SS' => 'SSD',
|
||
'ST' => 'STP',
|
||
'SV' => 'SLV',
|
||
'SX' => 'SXM',
|
||
'SY' => 'SYR',
|
||
'SZ' => 'SWZ',
|
||
'TC' => 'TCA',
|
||
'TD' => 'TCD',
|
||
'TF' => 'ATF',
|
||
'TG' => 'TGO',
|
||
'TH' => 'THA',
|
||
'TJ' => 'TJK',
|
||
'TK' => 'TKL',
|
||
'TL' => 'TLS',
|
||
'TM' => 'TKM',
|
||
'TN' => 'TUN',
|
||
'TO' => 'TON',
|
||
'TR' => 'TUR',
|
||
'TT' => 'TTO',
|
||
'TV' => 'TUV',
|
||
'TW' => 'TWN',
|
||
'TZ' => 'TZA',
|
||
'UA' => 'UKR',
|
||
'UG' => 'UGA',
|
||
'UM' => 'UMI',
|
||
'US' => 'USA',
|
||
'UY' => 'URY',
|
||
'UZ' => 'UZB',
|
||
'VA' => 'VAT',
|
||
'VC' => 'VCT',
|
||
'VE' => 'VEN',
|
||
'VG' => 'VGB',
|
||
'VI' => 'VIR',
|
||
'VN' => 'VNM',
|
||
'VU' => 'VUT',
|
||
'WF' => 'WLF',
|
||
'WS' => 'WSM',
|
||
'YE' => 'YEM',
|
||
'YT' => 'MYT',
|
||
'ZA' => 'ZAF',
|
||
'ZM' => 'ZMB',
|
||
'ZW' => 'ZWE',
|
||
'AN' => 'ANT',
|
||
'UK' => 'GBR',
|
||
'XK' => 'XKX',
|
||
);
|
||
// Enrichir / corriger depuis BD (source de vérité après script pay_iso3)
|
||
if (function_exists('fxChronotrackApiSyncPaysHasIso3Column') && fxChronotrackApiSyncPaysHasIso3Column()) {
|
||
global $objDatabase;
|
||
if (isset($objDatabase) && is_object($objDatabase)) {
|
||
$arrRows = $objDatabase->fxGetResults(
|
||
"SELECT pay_iso, pay_iso3 FROM inscriptions_pays"
|
||
. " WHERE pay_iso IS NOT NULL AND TRIM(pay_iso) <> ''"
|
||
. " AND pay_iso3 IS NOT NULL AND TRIM(pay_iso3) <> ''"
|
||
);
|
||
if (is_array($arrRows)) {
|
||
for ($i = 1; $i <= count($arrRows); $i++) {
|
||
$str2 = strtoupper(trim((string)($arrRows[$i]['pay_iso'] ?? '')));
|
||
$str3 = strtoupper(trim((string)($arrRows[$i]['pay_iso3'] ?? '')));
|
||
if (strlen($str2) === 2 && strlen($str3) === 3) {
|
||
$tabMap[$str2] = $str3;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return $tabMap;
|
||
}
|
||
|
||
function fxChronotrackApiSyncIso2ToIso3($strIso2) {
|
||
$strIso2 = strtoupper(trim((string)$strIso2));
|
||
if ($strIso2 === '') {
|
||
return '';
|
||
}
|
||
if (strlen($strIso2) === 3 && ctype_alpha($strIso2)) {
|
||
return $strIso2;
|
||
}
|
||
if (strlen($strIso2) !== 2) {
|
||
return '';
|
||
}
|
||
$tabMap = fxChronotrackApiSyncIso2ToIso3Map();
|
||
return isset($tabMap[$strIso2]) ? $tabMap[$strIso2] : '';
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — ISO-2 pays (pay_iso BD).
|
||
* Preuve calibrage 2026-08-11 : CT Athlete Info / export stockent location_country + COUNTRY_CODE = « CA » (pas CAN).
|
||
*/
|
||
function fxChronotrackApiSyncResolveCountryIso2(array $arrRow) {
|
||
$strIso2 = strtoupper(trim((string)($arrRow['country_iso2'] ?? '')));
|
||
if (strlen($strIso2) === 2 && ctype_alpha($strIso2)) {
|
||
return $strIso2;
|
||
}
|
||
return '';
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — ISO-3 (pay_iso3 / map) — utile logs / exports résultats CTLIVE.
|
||
* Pour l’écriture entry Athlete Info : préférer ResolveCountryIso2().
|
||
*/
|
||
function fxChronotrackApiSyncResolveCountryCode(array $arrRow) {
|
||
$strIso3 = strtoupper(trim((string)($arrRow['country_iso3'] ?? '')));
|
||
if (strlen($strIso3) === 3 && ctype_alpha($strIso3)) {
|
||
return $strIso3;
|
||
}
|
||
$strIso2 = fxChronotrackApiSyncResolveCountryIso2($arrRow);
|
||
if ($strIso2 === '') {
|
||
$strIso2 = strtoupper(trim((string)($arrRow['country_iso2'] ?? '')));
|
||
}
|
||
if (strlen($strIso2) === 3 && ctype_alpha($strIso2)) {
|
||
return $strIso2;
|
||
}
|
||
return fxChronotrackApiSyncIso2ToIso3($strIso2);
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — libellé pays (country_name CT / export COUNTRY_NAME).
|
||
*/
|
||
function fxChronotrackApiSyncResolveCountryLabel(array $arrRow) {
|
||
$strName = trim((string)($arrRow['country_name'] ?? ''));
|
||
if ($strName !== '') {
|
||
return $strName;
|
||
}
|
||
$strIso2 = fxChronotrackApiSyncResolveCountryIso2($arrRow);
|
||
static $tabByIso2 = array(
|
||
'CA' => 'Canada',
|
||
'US' => 'United States',
|
||
'MX' => 'Mexico',
|
||
'FR' => 'France',
|
||
'GB' => 'United Kingdom',
|
||
'BE' => 'Belgium',
|
||
'CH' => 'Switzerland',
|
||
'DE' => 'Germany',
|
||
'ES' => 'Spain',
|
||
'IT' => 'Italy',
|
||
'AU' => 'Australia',
|
||
'NZ' => 'New Zealand',
|
||
'BR' => 'Brazil',
|
||
'JP' => 'Japan',
|
||
'CN' => 'China',
|
||
'NL' => 'Netherlands',
|
||
'IE' => 'Ireland',
|
||
'PT' => 'Portugal',
|
||
'PL' => 'Poland',
|
||
'AT' => 'Austria',
|
||
'SE' => 'Sweden',
|
||
'NO' => 'Norway',
|
||
'DK' => 'Denmark',
|
||
'FI' => 'Finland',
|
||
);
|
||
if ($strIso2 !== '' && isset($tabByIso2[$strIso2])) {
|
||
return $tabByIso2[$strIso2];
|
||
}
|
||
$strIso3 = fxChronotrackApiSyncResolveCountryCode($arrRow);
|
||
static $tabByIso3 = array(
|
||
'CAN' => 'Canada',
|
||
'USA' => 'United States',
|
||
'FRA' => 'France',
|
||
);
|
||
if ($strIso3 !== '' && isset($tabByIso3[$strIso3])) {
|
||
return $tabByIso3[$strIso3];
|
||
}
|
||
return $strIso2 !== '' ? $strIso2 : $strIso3;
|
||
}
|
||
|
||
function fxChronotrackApiSyncMapEntryStatus($strMs1Status, $blnCancelled = false) {
|
||
if ($blnCancelled) {
|
||
return 'WITHDRAWN';
|
||
}
|
||
$strMs1Status = strtoupper(trim((string)$strMs1Status));
|
||
switch ($strMs1Status) {
|
||
case 'DQ':
|
||
return 'DQ';
|
||
case 'DNS':
|
||
return 'DNS';
|
||
case 'NP':
|
||
return 'NP';
|
||
case 'ABANDON':
|
||
return 'DNF';
|
||
case 'UNRANKED':
|
||
return 'UNRANKED';
|
||
case 'DEFERRED':
|
||
return 'DEFERRED';
|
||
case 'CONF':
|
||
default:
|
||
return 'CONF';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* MSIN-4567 — MS1 par_sexe → ChronoTrack athlete_sex (enum API /meta).
|
||
* CT : M=Male, F=Female, NB=Non-Binary, NOT SPECIFIED=Unspecified.
|
||
*/
|
||
function fxChronotrackApiSyncNormalizeSex($strSexe) {
|
||
$strSexe = strtolower(trim((string)$strSexe));
|
||
if ($strSexe === 'f') {
|
||
return 'F';
|
||
}
|
||
if ($strSexe === 'h' || $strSexe === 'm') {
|
||
return 'M';
|
||
}
|
||
if ($strSexe === 'n') {
|
||
return 'NB';
|
||
}
|
||
if ($strSexe === '' || $strSexe === 'a') {
|
||
return 'NOT SPECIFIED';
|
||
}
|
||
return '';
|
||
}
|
||
|
||
function fxChronotrackApiSyncHasValidSex($strSexe) {
|
||
return fxChronotrackApiSyncNormalizeSex($strSexe) !== '';
|
||
}
|
||
|
||
/**
|
||
* MSIN-4328 / MSIN-4444 — dossard ChronoTrack = toujours p.no_bib.
|
||
* Jamais ec.no_equipe (n° d’équipe MS1) : en mode « 1 équipe = 1 dossard »
|
||
* les deux coïncident souvent au 1er assign, puis no_bib peut diverger (ex. 535 vs 507).
|
||
*/
|
||
function fxChronotrackApiSyncExtractBib(array $arrRow) {
|
||
$strBib = trim((string)($arrRow['no_bib'] ?? ''));
|
||
if ($strBib === '' || $strBib === '0') {
|
||
return '';
|
||
}
|
||
// Aligné affichage MS1 (fxShowBibNumber) : enlever préfixe epr_id-
|
||
$intEprId = intval($arrRow['epr_id'] ?? 0);
|
||
if ($intEprId > 0) {
|
||
$strBib = str_replace($intEprId . '-', '', $strBib);
|
||
}
|
||
$strBib = str_replace('-', '', $strBib);
|
||
$strBib = trim($strBib);
|
||
if ($strBib === '' || $strBib === '0') {
|
||
return '';
|
||
}
|
||
return $strBib;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4328 — conflit CT « tags / bib already assigned » (swap dossards).
|
||
*/
|
||
function fxChronotrackApiSyncIsBibConflictMessage($strMessage) {
|
||
$str = strtolower((string)$strMessage);
|
||
if ($str === '') {
|
||
return false;
|
||
}
|
||
if (strpos($str, 'already assigned') !== false) {
|
||
return true;
|
||
}
|
||
if (strpos($str, 'tags are already') !== false) {
|
||
return true;
|
||
}
|
||
if (strpos($str, 'bib') !== false && strpos($str, 'already') !== false) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function fxChronotrackApiSyncPayloadWithoutBib(array $arrPayload) {
|
||
$arrOut = $arrPayload;
|
||
unset($arrOut['bib']);
|
||
return $arrOut;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4328 — libérer le dossard CT (swap) : bib vide.
|
||
*/
|
||
function fxChronotrackApiSyncPayloadClearBib(array $arrPayload) {
|
||
$arrOut = $arrPayload;
|
||
$arrOut['bib'] = '';
|
||
return $arrOut;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — comparer dossards CT/MS1 (2342 == 02342).
|
||
*/
|
||
function fxChronotrackApiSyncBibNorm($strBib) {
|
||
$str = trim((string)$strBib);
|
||
if ($str === '') {
|
||
return '';
|
||
}
|
||
if (ctype_digit($str)) {
|
||
return (string)intval($str);
|
||
}
|
||
return strtolower($str);
|
||
}
|
||
|
||
function fxChronotrackApiSyncParticipantLabel(array $arrRow) {
|
||
return trim(trim((string)($arrRow['par_prenom'] ?? '')) . ' ' . trim((string)($arrRow['par_nom'] ?? '')));
|
||
}
|
||
|
||
/**
|
||
* MSIN-4328 — libellé épreuve MS1 (type — nom), pour messages d’exclusion.
|
||
*/
|
||
function fxChronotrackApiSyncEpreuveLabel(array $arrRow) {
|
||
$strNom = trim((string)($arrRow['epr_nom'] ?? ''));
|
||
$strType = trim((string)($arrRow['epr_type'] ?? ''));
|
||
if ($strType !== '' && $strNom !== '') {
|
||
return $strType . ' — ' . $strNom;
|
||
}
|
||
if ($strNom !== '') {
|
||
return $strNom;
|
||
}
|
||
return $strType;
|
||
}
|
||
|
||
function fxChronotrackApiSyncIsTeamRow(array $arrRow) {
|
||
return intval($arrRow['pec_equipe'] ?? 0) === 1 || intval($arrRow['par_equipe'] ?? 0) === 1;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4444 — nom d’équipe MS1 (commande, sinon participant).
|
||
*/
|
||
function fxChronotrackApiSyncTeamName(array $arrRow) {
|
||
$strName = trim((string)($arrRow['pec_nom_equipe'] ?? ''));
|
||
if ($strName === '') {
|
||
$strName = trim((string)($arrRow['par_nom_equipe'] ?? ''));
|
||
}
|
||
return $strName;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4444 — clé d’équipe = pec_id (commande).
|
||
*/
|
||
function fxChronotrackApiSyncTeamGroupKey(array $arrRow) {
|
||
$intPecId = intval($arrRow['pec_id'] ?? 0);
|
||
if ($intPecId > 0) {
|
||
return 'pec:' . $intPecId;
|
||
}
|
||
return 'par:' . intval($arrRow['par_id'] ?? 0);
|
||
}
|
||
|
||
/**
|
||
* MSIN-4444 — 1 entrée CT par équipe : capitaine (rol_id=1), sinon 1er membre.
|
||
*/
|
||
function fxChronotrackApiSyncPickTeamRepresentative(array $tabMembers) {
|
||
$arrFirst = null;
|
||
foreach ($tabMembers as $arrRow) {
|
||
if (!is_array($arrRow)) {
|
||
continue;
|
||
}
|
||
if ($arrFirst === null) {
|
||
$arrFirst = $arrRow;
|
||
}
|
||
if (intval($arrRow['rol_id'] ?? 0) === 1) {
|
||
return $arrRow;
|
||
}
|
||
}
|
||
return $arrFirst;
|
||
}
|
||
|
||
function fxChronotrackApiSyncFormatRoleLabel($intRolId) {
|
||
switch (intval($intRolId)) {
|
||
case 1:
|
||
return 'Capitaine';
|
||
case 2:
|
||
return 'Membre';
|
||
default:
|
||
return $intRolId > 0 ? ('Rôle ' . intval($intRolId)) : '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — code pays présent dans un payload CT (location_country prioritaire).
|
||
* Calibrage : CT stocke ISO-2 dans location_country (ex. CA).
|
||
*/
|
||
function fxChronotrackApiSyncCountryFromPayload(array $arrPayload) {
|
||
$str = trim((string)($arrPayload['location_country'] ?? ''));
|
||
if ($str !== '') {
|
||
return $str;
|
||
}
|
||
return trim((string)($arrPayload['country_code'] ?? ''));
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — couverture pays sur les transférables (warning, pas blocage).
|
||
*
|
||
* @param array $tabTransferable items classify (row + payload optionnel)
|
||
* @return array{total:int,with_country:int,without_country:int,no_pay_id:int}
|
||
*/
|
||
function fxChronotrackApiSyncCountryCoverageFromTransferable(array $tabTransferable) {
|
||
$intTotal = count($tabTransferable);
|
||
$intWith = 0;
|
||
$intNoPay = 0;
|
||
foreach ($tabTransferable as $arrItem) {
|
||
if (!is_array($arrItem)) {
|
||
continue;
|
||
}
|
||
$arrRow = is_array($arrItem['row'] ?? null) ? $arrItem['row'] : array();
|
||
if (intval($arrRow['pay_id'] ?? 0) <= 0) {
|
||
$intNoPay++;
|
||
}
|
||
$strCode = '';
|
||
if (is_array($arrItem['payload'] ?? null)) {
|
||
$strCode = fxChronotrackApiSyncCountryFromPayload($arrItem['payload']);
|
||
}
|
||
if ($strCode === '') {
|
||
$strCode = fxChronotrackApiSyncResolveCountryCode($arrRow);
|
||
}
|
||
if ($strCode !== '') {
|
||
$intWith++;
|
||
}
|
||
}
|
||
return array(
|
||
'total' => $intTotal,
|
||
'with_country' => $intWith,
|
||
'without_country' => max(0, $intTotal - $intWith),
|
||
'no_pay_id' => $intNoPay,
|
||
);
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — fragment log « pays=CAN » / « pays=— ».
|
||
*/
|
||
function fxChronotrackApiSyncLogCountryFragment(array $arrPayload = array(), array $arrRow = array()) {
|
||
$str = fxChronotrackApiSyncCountryFromPayload($arrPayload);
|
||
if ($str === '' && count($arrRow) > 0) {
|
||
$str = fxChronotrackApiSyncResolveCountryCode($arrRow);
|
||
}
|
||
return 'pays=' . ($str !== '' ? $str : '—');
|
||
}
|
||
|
||
function fxChronotrackApiSyncParticipantSummary(array $arrRow) {
|
||
return array(
|
||
'par_id' => intval($arrRow['par_id'] ?? 0),
|
||
'external_id' => (string)intval($arrRow['par_id_original'] ?? 0),
|
||
'name' => fxChronotrackApiSyncParticipantLabel($arrRow),
|
||
'no_bib' => fxChronotrackApiSyncExtractBib($arrRow),
|
||
'par_sexe' => trim((string)($arrRow['par_sexe'] ?? '')),
|
||
'pec_id' => intval($arrRow['pec_id'] ?? 0),
|
||
'is_team' => fxChronotrackApiSyncIsTeamRow($arrRow),
|
||
'team_name' => fxChronotrackApiSyncTeamName($arrRow),
|
||
'no_equipe' => trim((string)($arrRow['no_equipe'] ?? '')),
|
||
'role_label' => fxChronotrackApiSyncFormatRoleLabel($arrRow['rol_id'] ?? 0),
|
||
);
|
||
}
|
||
|
||
function fxChronotrackApiSyncNormalizeBirthdate($strDob) {
|
||
$strDob = trim((string)$strDob);
|
||
if ($strDob === '' || $strDob === '0000-00-00') {
|
||
return '';
|
||
}
|
||
$intTs = strtotime($strDob);
|
||
if ($intTs === false) {
|
||
return '';
|
||
}
|
||
return date('Y-m-d', $intTs);
|
||
}
|
||
|
||
function fxChronotrackApiSyncPaysHasIso3Column() {
|
||
static $blnHas = null;
|
||
if ($blnHas !== null) {
|
||
return $blnHas;
|
||
}
|
||
global $objDatabase;
|
||
$arrRow = $objDatabase->fxGetRow("SHOW COLUMNS FROM inscriptions_pays LIKE 'pay_iso3'");
|
||
$blnHas = ($arrRow !== null);
|
||
return $blnHas;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4461 — clé CT entry_note_{label} (support ChronoTrack).
|
||
*/
|
||
function fxChronotrackApiSyncEntryNoteKey($strLabel, $intQueId) {
|
||
$strLabel = trim((string)$strLabel);
|
||
$strLabel = preg_replace('/\s+/u', '_', $strLabel);
|
||
$strLabel = preg_replace('/["\\\\\x00-\x1F]+/u', '', $strLabel);
|
||
$strLabel = trim($strLabel, '_');
|
||
if ($strLabel === '') {
|
||
$strLabel = 'q' . intval($intQueId);
|
||
}
|
||
return 'entry_note_' . $strLabel;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4461 — index des réponses questions d’un événement (1 requête / eve_id).
|
||
*
|
||
* Périmètre actuel : TOUTES les questions actives de l’événement (pas seulement que_rapport).
|
||
* Si le payload devient trop lourd (beaucoup de questions / événements volumineux),
|
||
* restreindre ici (ex. que_rapport = 1, liste blanche, ou exclusion des validations techniques).
|
||
*
|
||
* @return array<int, array<int, array{par_id:int,que_id:int,label:string,value:string}>>
|
||
* indexé par pec_id
|
||
*/
|
||
function fxChronotrackApiSyncLoadMs1QuestionsIndex($intEveId) {
|
||
global $objDatabase;
|
||
|
||
static $tabCache = array();
|
||
$intEveId = intval($intEveId);
|
||
if ($intEveId <= 0) {
|
||
return array();
|
||
}
|
||
if (isset($tabCache[$intEveId])) {
|
||
return $tabCache[$intEveId];
|
||
}
|
||
|
||
$sql = "SELECT rq.pec_id, rq.par_id, rq.que_id,"
|
||
. " TRIM(COALESCE("
|
||
. " NULLIF(iq.que_rapport_label_fr, ''),"
|
||
. " NULLIF(rq.que_question_fr, ''),"
|
||
. " NULLIF(iq.que_question_fr, ''),"
|
||
. " '')) AS note_label,"
|
||
. " TRIM(COALESCE("
|
||
. " NULLIF(rq.que_choix_fr, ''),"
|
||
. " NULLIF(rq.pqu_note, ''),"
|
||
. " NULLIF(rq.que_choix_en, ''),"
|
||
. " '')) AS note_value"
|
||
. " FROM resultats_questions rq"
|
||
. " INNER JOIN inscriptions_questions iq ON iq.que_id = rq.que_id AND iq.eve_id = " . $intEveId
|
||
. " WHERE rq.que_actif = 1"
|
||
. " ORDER BY rq.pec_id, rq.que_id";
|
||
|
||
$tabRows = $objDatabase->fxGetResults($sql);
|
||
$tabIndex = array();
|
||
if (is_array($tabRows)) {
|
||
for ($i = 1; $i <= count($tabRows); $i++) {
|
||
$arrQ = $tabRows[$i];
|
||
$intPecId = intval($arrQ['pec_id'] ?? 0);
|
||
$strValue = trim((string)($arrQ['note_value'] ?? ''));
|
||
if ($intPecId <= 0 || $strValue === '') {
|
||
continue;
|
||
}
|
||
if (!isset($tabIndex[$intPecId])) {
|
||
$tabIndex[$intPecId] = array();
|
||
}
|
||
$tabIndex[$intPecId][] = array(
|
||
'par_id' => intval($arrQ['par_id'] ?? 0),
|
||
'que_id' => intval($arrQ['que_id'] ?? 0),
|
||
'label' => trim((string)($arrQ['note_label'] ?? '')),
|
||
'value' => $strValue,
|
||
);
|
||
}
|
||
}
|
||
|
||
$tabCache[$intEveId] = $tabIndex;
|
||
return $tabIndex;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4461 — map entry_note_* pour un participant (réponses épreuve + participant).
|
||
* resultats_questions.par_id = par_id_original (legacy) ; par_id 0 = question niveau épreuve.
|
||
*/
|
||
function fxChronotrackApiSyncEntryNotesForParticipant(array $arrRow) {
|
||
$intEveId = intval($arrRow['eve_id'] ?? 0);
|
||
$intPecId = intval($arrRow['pec_id'] ?? 0);
|
||
if ($intEveId <= 0 || $intPecId <= 0) {
|
||
return array();
|
||
}
|
||
|
||
$tabIndex = fxChronotrackApiSyncLoadMs1QuestionsIndex($intEveId);
|
||
if (!isset($tabIndex[$intPecId]) || !is_array($tabIndex[$intPecId])) {
|
||
return array();
|
||
}
|
||
|
||
$intParOrig = intval($arrRow['par_id_original'] ?? 0);
|
||
$intParId = intval($arrRow['par_id'] ?? 0);
|
||
$tabNotes = array();
|
||
$tabUsedKeys = array();
|
||
|
||
foreach ($tabIndex[$intPecId] as $arrQ) {
|
||
$intQPar = intval($arrQ['par_id'] ?? 0);
|
||
// Question d’épreuve (par_id vide) ou réponse de ce participant
|
||
if ($intQPar > 0 && $intQPar !== $intParOrig && $intQPar !== $intParId) {
|
||
continue;
|
||
}
|
||
$intQueId = intval($arrQ['que_id'] ?? 0);
|
||
$strKey = fxChronotrackApiSyncEntryNoteKey($arrQ['label'] ?? '', $intQueId);
|
||
if (isset($tabUsedKeys[$strKey])) {
|
||
$strKey = 'entry_note_q' . $intQueId;
|
||
}
|
||
$tabUsedKeys[$strKey] = true;
|
||
$tabNotes[$strKey] = (string)$arrQ['value'];
|
||
}
|
||
|
||
return $tabNotes;
|
||
}
|
||
|
||
function fxChronotrackApiSyncResolveRegChoiceIdForRace($intCtEventId, $intCtRaceId) {
|
||
static $tabCache = array();
|
||
$strKey = intval($intCtEventId) . ':' . intval($intCtRaceId);
|
||
if (array_key_exists($strKey, $tabCache)) {
|
||
return $tabCache[$strKey];
|
||
}
|
||
|
||
$tabCache[$strKey] = 0;
|
||
$arrRaces = fxChronotrackApiFetchRacesForEvent($intCtEventId);
|
||
if (($arrRaces['state'] ?? '') !== 'ok' || empty($arrRaces['races'])) {
|
||
return 0;
|
||
}
|
||
|
||
foreach ($arrRaces['races'] as $arrRace) {
|
||
if (intval($arrRace['ct_race_id'] ?? 0) !== intval($intCtRaceId)) {
|
||
continue;
|
||
}
|
||
$arrRaw = isset($arrRace['raw']) && is_array($arrRace['raw']) ? $arrRace['raw'] : array();
|
||
$arrChoices = fxChronotrackApiCollectRegChoiceEntities($arrRaw);
|
||
if (count($arrChoices) === 0 && isset($arrRaw['reg_choice_id'])) {
|
||
$tabCache[$strKey] = intval($arrRaw['reg_choice_id']);
|
||
return $tabCache[$strKey];
|
||
}
|
||
foreach ($arrChoices as $arrChoice) {
|
||
if (!is_array($arrChoice) || empty($arrChoice['reg_choice_id'])) {
|
||
continue;
|
||
}
|
||
$tabCache[$strKey] = intval($arrChoice['reg_choice_id']);
|
||
return $tabCache[$strKey];
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
function fxChronotrackApiSyncBuildEntryPayload(array $arrRow, array $arrRaceMap, $intCtEventId = 0) {
|
||
$intEprId = intval($arrRow['epr_id'] ?? 0);
|
||
$intCtRaceId = intval($arrRaceMap[$intEprId]['ct_race_id'] ?? 0);
|
||
if ($intCtRaceId <= 0) {
|
||
return null;
|
||
}
|
||
|
||
$intExternalId = intval($arrRow['par_id_original'] ?? 0);
|
||
if ($intExternalId <= 0) {
|
||
return null;
|
||
}
|
||
|
||
// MSIN-4574 — pay_iso (ISO-2) = source pour location_country CT
|
||
$strCountry = fxChronotrackApiSyncResolveCountryIso2($arrRow);
|
||
$strState = strtoupper(trim((string)($arrRow['state_iso'] ?? '')));
|
||
|
||
$strSex = fxChronotrackApiSyncNormalizeSex($arrRow['par_sexe'] ?? '');
|
||
if ($strSex === '') {
|
||
return null;
|
||
}
|
||
|
||
$strBib = fxChronotrackApiSyncExtractBib($arrRow);
|
||
if ($strBib === '') {
|
||
return null;
|
||
}
|
||
|
||
$arrEntry = array(
|
||
'external_id' => (string)$intExternalId,
|
||
'race_id' => $intCtRaceId,
|
||
'first_name' => trim((string)($arrRow['par_prenom'] ?? '')),
|
||
'last_name' => trim((string)($arrRow['par_nom'] ?? '')),
|
||
'sex' => $strSex,
|
||
);
|
||
|
||
// MSIN-4444 — inscription équipe : prénom = EQ., nom = nom d'équipe
|
||
if (fxChronotrackApiSyncIsTeamRow($arrRow)) {
|
||
$strTeamName = fxChronotrackApiSyncTeamName($arrRow);
|
||
if ($strTeamName === '') {
|
||
return null;
|
||
}
|
||
$arrEntry['first_name'] = 'EQ.';
|
||
$arrEntry['last_name'] = $strTeamName;
|
||
}
|
||
|
||
if ($intCtEventId > 0) {
|
||
$arrEntry['event_id'] = $intCtEventId;
|
||
}
|
||
|
||
$intRegChoiceId = fxChronotrackApiSyncResolveRegChoiceIdForRace($intCtEventId, $intCtRaceId);
|
||
if ($intRegChoiceId > 0) {
|
||
$arrEntry['reg_choice_id'] = $intRegChoiceId;
|
||
}
|
||
|
||
$strDob = fxChronotrackApiSyncNormalizeBirthdate($arrRow['par_naissance'] ?? '');
|
||
if ($strDob !== '') {
|
||
$arrEntry['birthdate'] = $strDob;
|
||
}
|
||
|
||
// MSIN-4574 — meta CT entry : location_* (pas street/city/country_code du format résultats).
|
||
// On envoie les clés meta + alias courts (CT accepte souvent les deux pour identité).
|
||
$strStreet = trim((string)($arrRow['par_adresse'] ?? ''));
|
||
if ($strStreet !== '') {
|
||
$arrEntry['location_street'] = $strStreet;
|
||
$arrEntry['street'] = $strStreet;
|
||
}
|
||
|
||
$strCity = trim((string)($arrRow['par_ville'] ?? ''));
|
||
if ($strCity !== '') {
|
||
$arrEntry['location_city'] = $strCity;
|
||
$arrEntry['city'] = $strCity;
|
||
}
|
||
|
||
$strPostal = trim((string)($arrRow['par_codepostal'] ?? ''));
|
||
if ($strPostal !== '') {
|
||
$arrEntry['location_postal_code'] = $strPostal;
|
||
$arrEntry['postal_code'] = $strPostal;
|
||
}
|
||
|
||
if ($strState !== '') {
|
||
$arrEntry['location_region'] = $strState;
|
||
$arrEntry['state_code'] = $strState;
|
||
}
|
||
|
||
// MSIN-4574 — pays / province (calibrage sonde lecture + export confirmed entries 2026-08-11) :
|
||
// location_country = ISO-2 (CA), location_region = pro_iso (QC),
|
||
// country_name = libellé, country_code = ISO-2 (comme export COUNTRY_CODE).
|
||
$strIso2 = fxChronotrackApiSyncResolveCountryIso2($arrRow);
|
||
$strCountryName = fxChronotrackApiSyncResolveCountryLabel($arrRow);
|
||
if ($strIso2 !== '') {
|
||
$arrEntry['location_country'] = $strIso2;
|
||
$arrEntry['country_code'] = $strIso2;
|
||
}
|
||
if ($strCountryName !== '') {
|
||
$arrEntry['country_name'] = $strCountryName;
|
||
}
|
||
|
||
$arrEntry['entry_status'] = fxChronotrackApiSyncMapEntryStatus(
|
||
$arrRow['par_statut_course'] ?? 'CONF',
|
||
intval($arrRow['is_cancelled'] ?? 0) === 1
|
||
);
|
||
$arrEntry['status'] = $arrEntry['entry_status'];
|
||
|
||
// MSIN-4574 — toujours envoyer le dossard s’il existe (CONF, DQ, DNS, etc.).
|
||
// Ancien garde-fou CONF-only : trop strict côté MS1 (ex. changement de bib sur DQ).
|
||
// Si CT refuse un statut précis, l’erreur remonte au push — on ne bloque plus ici.
|
||
if ($strBib !== '') {
|
||
$arrEntry['bib'] = $strBib;
|
||
}
|
||
|
||
// MSIN-4328 — Dossard récupéré (MS1) → Check In Status CT (entry_check_in = "1" / null)
|
||
$arrEntry['entry_check_in'] = (intval($arrRow['no_bib_remis'] ?? 0) === 1) ? '1' : null;
|
||
|
||
// MSIN-4461 — questions MS1 → notes CT (entry_note_{label}).
|
||
// Toutes les réponses actives pour l’instant ; filtrer plus tard si trop lourd (voir LoadMs1QuestionsIndex).
|
||
foreach (fxChronotrackApiSyncEntryNotesForParticipant($arrRow) as $strNoteKey => $strNoteValue) {
|
||
$arrEntry[$strNoteKey] = $strNoteValue;
|
||
}
|
||
|
||
return $arrEntry;
|
||
}
|
||
|
||
function fxChronotrackApiSyncPayloadForPost(array $arrPayload) {
|
||
$arrOut = $arrPayload;
|
||
unset($arrOut['entry_id']);
|
||
return $arrOut;
|
||
}
|
||
|
||
function fxChronotrackApiSyncPayloadForPut(array $arrPayload) {
|
||
$arrOut = $arrPayload;
|
||
unset($arrOut['entry_id']);
|
||
return $arrOut;
|
||
}
|
||
|
||
function fxChronotrackApiSyncEnrichPayloadWithCtEntry(array $arrPayload, $arrCtEntity) {
|
||
if (!is_array($arrCtEntity)) {
|
||
return $arrPayload;
|
||
}
|
||
$strEntryId = fxChronotrackApiEntityId($arrCtEntity);
|
||
if ($strEntryId !== '') {
|
||
$arrPayload['entry_id'] = $strEntryId;
|
||
}
|
||
return $arrPayload;
|
||
}
|
||
|