This commit updates the ChronoTrack API to improve event handling and synchronization features. Changes include enhanced messaging for closed events, adjustments to the push panel functionality, and refined status handling for event synchronization. The logic for managing event states is streamlined, allowing for clearer user interactions and better management of synchronization processes. These improvements aim to enhance the overall usability and effectiveness of the ChronoTrack API in event management.
414 lines
13 KiB
PHP
414 lines
13 KiB
PHP
<?php
|
|
/**
|
|
* MSIN API ChronoTrack — Phase 2a : lecture MS1, diff, push manuel entries.
|
|
*/
|
|
|
|
define('MSIN_API_CHRONOTRACK_SYNC_BATCH_SIZE', 25);
|
|
|
|
function fxChronotrackApiSyncIso2ToIso3($strIso2) {
|
|
static $tabMap = null;
|
|
if ($tabMap === null) {
|
|
$tabMap = array(
|
|
'CA' => 'CAN', 'US' => 'USA', 'MX' => 'MEX', 'FR' => 'FRA', 'GB' => 'GBR', 'UK' => 'GBR',
|
|
'DE' => 'DEU', 'ES' => 'ESP', 'IT' => 'ITA', 'BE' => 'BEL', 'CH' => 'CHE', 'AU' => 'AUS',
|
|
'JP' => 'JPN', 'CN' => 'CHN', 'BR' => 'BRA', 'NL' => 'NLD', 'SE' => 'SWE', 'NO' => 'NOR',
|
|
'DK' => 'DNK', 'FI' => 'FIN', 'IE' => 'IRL', 'PT' => 'PRT', 'PL' => 'POL', 'AT' => 'AUT',
|
|
'NZ' => 'NZL', 'IN' => 'IND', 'KR' => 'KOR', 'AD' => 'AND', 'AE' => 'ARE', 'AF' => 'AFG',
|
|
);
|
|
}
|
|
$strIso2 = strtoupper(trim((string)$strIso2));
|
|
if ($strIso2 === '') {
|
|
return '';
|
|
}
|
|
if (strlen($strIso2) === 3) {
|
|
return $strIso2;
|
|
}
|
|
return isset($tabMap[$strIso2]) ? $tabMap[$strIso2] : '';
|
|
}
|
|
|
|
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';
|
|
}
|
|
}
|
|
|
|
function fxChronotrackApiSyncNormalizeSex($strSexe) {
|
|
$strSexe = strtolower(trim((string)$strSexe));
|
|
if ($strSexe === 'f' || $strSexe === 'F') {
|
|
return 'F';
|
|
}
|
|
if ($strSexe === 'h' || $strSexe === 'm' || $strSexe === 'M') {
|
|
return 'M';
|
|
}
|
|
if ($strSexe === '') {
|
|
return 'U';
|
|
}
|
|
return strtoupper(substr($strSexe, 0, 1));
|
|
}
|
|
|
|
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 fxChronotrackApiSyncLog($intEveId, $intParId, $strAction, $strStatus, $strMessage) {
|
|
global $objDatabase;
|
|
|
|
$sql = "INSERT INTO api_chronotrack_sync_log SET eve_id = " . intval($intEveId)
|
|
. ", par_id = " . ($intParId > 0 ? intval($intParId) : 'NULL')
|
|
. ", action = '" . $objDatabase->fxEscape(substr(trim($strAction), 0, 32)) . "'"
|
|
. ", status = '" . $objDatabase->fxEscape($strStatus === 'ok' ? 'ok' : 'error') . "'"
|
|
. ", message = '" . $objDatabase->fxEscape(substr(trim($strMessage), 0, 65000)) . "'"
|
|
. ", created_at = '" . fxGetDateTime() . "'";
|
|
$objDatabase->fxQuery($sql);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
function fxChronotrackApiSyncLoadMs1Participants($intEveId) {
|
|
global $objDatabase;
|
|
|
|
$intEveId = intval($intEveId);
|
|
if ($intEveId <= 0) {
|
|
return array();
|
|
}
|
|
|
|
$strCountryCols = " (SELECT pay_iso FROM inscriptions_pays WHERE pay_id = p.pay_id) AS country_iso2";
|
|
if (fxChronotrackApiSyncPaysHasIso3Column()) {
|
|
$strCountryCols = " (SELECT pay_iso3 FROM inscriptions_pays WHERE pay_id = p.pay_id) AS country_iso3,"
|
|
. " (SELECT pay_iso FROM inscriptions_pays WHERE pay_id = p.pay_id) AS country_iso2";
|
|
}
|
|
|
|
$sql = "SELECT p.par_id, p.par_id_original, p.epr_id, p.par_prenom, p.par_nom, p.par_sexe,"
|
|
. " p.par_naissance, p.no_bib, p.par_statut_course, p.par_ville, p.par_adresse, p.par_codepostal,"
|
|
. " p.is_cancelled,"
|
|
. " (SELECT pro_iso FROM inscriptions_provinces WHERE pro_id = p.pro_id) AS state_iso,"
|
|
. $strCountryCols
|
|
. " FROM resultats_participants p"
|
|
. " JOIN resultats_epreuves_commandees ec ON p.pec_id = ec.pec_id_original"
|
|
. " WHERE ec.pec_actif = 1 AND ec.is_cancelled = 0 AND p.is_cancelled = 0"
|
|
. " AND p.eve_id = " . $intEveId
|
|
. " ORDER BY p.par_id_original, p.par_id";
|
|
|
|
$tabRows = $objDatabase->fxGetResults($sql);
|
|
if (!is_array($tabRows)) {
|
|
return array();
|
|
}
|
|
|
|
$tabOut = array();
|
|
for ($i = 1; $i <= count($tabRows); $i++) {
|
|
$tabOut[] = $tabRows[$i];
|
|
}
|
|
return $tabOut;
|
|
}
|
|
|
|
function fxChronotrackApiSyncBuildEntryPayload(array $arrRow, array $arrRaceMap) {
|
|
$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;
|
|
}
|
|
|
|
$strCountry = strtoupper(trim((string)($arrRow['country_iso3'] ?? '')));
|
|
if ($strCountry === '') {
|
|
$strCountry = fxChronotrackApiSyncIso2ToIso3($arrRow['country_iso2'] ?? '');
|
|
}
|
|
$strState = strtoupper(trim((string)($arrRow['state_iso'] ?? '')));
|
|
|
|
$arrEntry = array(
|
|
'external_id' => (string)$intExternalId,
|
|
'race_id' => $intCtRaceId,
|
|
'first_name' => trim((string)($arrRow['par_prenom'] ?? '')),
|
|
'last_name' => trim((string)($arrRow['par_nom'] ?? '')),
|
|
'sex' => fxChronotrackApiSyncNormalizeSex($arrRow['par_sexe'] ?? ''),
|
|
'entry_status' => fxChronotrackApiSyncMapEntryStatus(
|
|
$arrRow['par_statut_course'] ?? '',
|
|
intval($arrRow['is_cancelled'] ?? 0) === 1
|
|
),
|
|
);
|
|
|
|
$strDob = fxChronotrackApiSyncNormalizeBirthdate($arrRow['par_naissance'] ?? '');
|
|
if ($strDob !== '') {
|
|
$arrEntry['birthdate'] = $strDob;
|
|
}
|
|
|
|
$strBib = trim((string)($arrRow['no_bib'] ?? ''));
|
|
if ($strBib !== '' && $strBib !== '0') {
|
|
$arrEntry['bib'] = $strBib;
|
|
}
|
|
|
|
$strStreet = trim((string)($arrRow['par_adresse'] ?? ''));
|
|
if ($strStreet !== '') {
|
|
$arrEntry['street'] = $strStreet;
|
|
}
|
|
|
|
$strCity = trim((string)($arrRow['par_ville'] ?? ''));
|
|
if ($strCity !== '') {
|
|
$arrEntry['city'] = $strCity;
|
|
}
|
|
|
|
$strPostal = trim((string)($arrRow['par_codepostal'] ?? ''));
|
|
if ($strPostal !== '') {
|
|
$arrEntry['postal_code'] = $strPostal;
|
|
}
|
|
|
|
if ($strState !== '') {
|
|
$arrEntry['state_code'] = $strState;
|
|
}
|
|
|
|
if ($strCountry !== '') {
|
|
$arrEntry['country_code'] = $strCountry;
|
|
}
|
|
|
|
return $arrEntry;
|
|
}
|
|
|
|
function fxChronotrackApiSyncFetchCtEntriesIndexed($intCtEventId) {
|
|
$intCtEventId = intval($intCtEventId);
|
|
$tabIndexed = array();
|
|
$intPage = 1;
|
|
$intPageSize = 100;
|
|
$intMaxPages = 50;
|
|
|
|
while ($intPage <= $intMaxPages) {
|
|
$arrApi = fxChronotrackApiOAuthApiGet('event/' . $intCtEventId . '/entry', array(
|
|
'page' => $intPage,
|
|
'page_size' => $intPageSize,
|
|
));
|
|
if ($arrApi['state'] !== 'ok') {
|
|
return array(
|
|
'state' => 'error',
|
|
'message' => $arrApi['message'] ?? 'Erreur lecture entries CT',
|
|
'entries' => $tabIndexed,
|
|
);
|
|
}
|
|
|
|
$arrEntities = fxChronotrackApiNormalizeEntityList($arrApi['json'], 'entry');
|
|
if (count($arrEntities) === 0) {
|
|
break;
|
|
}
|
|
|
|
foreach ($arrEntities as $arrEntity) {
|
|
if (!is_array($arrEntity)) {
|
|
continue;
|
|
}
|
|
$strExt = fxChronotrackApiEntityExternalId($arrEntity);
|
|
if ($strExt === '') {
|
|
continue;
|
|
}
|
|
$tabIndexed[$strExt] = $arrEntity;
|
|
}
|
|
|
|
if (count($arrEntities) < $intPageSize) {
|
|
break;
|
|
}
|
|
$intPage++;
|
|
}
|
|
|
|
return array(
|
|
'state' => 'ok',
|
|
'message' => '',
|
|
'entries' => $tabIndexed,
|
|
);
|
|
}
|
|
|
|
function fxChronotrackApiSyncBuildPreview($intEveId) {
|
|
$arrConfig = fxChronotrackApiConfigGet($intEveId);
|
|
if ($arrConfig === null) {
|
|
return array('state' => 'error', 'message' => 'Événement non lié à ChronoTrack');
|
|
}
|
|
|
|
if (!fxChronotrackApiSettingsConfigured()) {
|
|
return array('state' => 'error', 'message' => fxChronotrackApiSettingsErrorMessage());
|
|
}
|
|
|
|
$intCtEventId = intval($arrConfig['ct_event_id']);
|
|
$arrRaceMap = fxChronotrackApiRaceMapGetForEvent($intEveId);
|
|
$tabParticipants = fxChronotrackApiSyncLoadMs1Participants($intEveId);
|
|
|
|
$intMs1Total = count($tabParticipants);
|
|
$intEligible = 0;
|
|
$intSkippedNoRace = 0;
|
|
$intSkippedNoExternal = 0;
|
|
|
|
$tabEligibleKeys = array();
|
|
foreach ($tabParticipants as $arrRow) {
|
|
$arrPayload = fxChronotrackApiSyncBuildEntryPayload($arrRow, $arrRaceMap);
|
|
if ($arrPayload === null) {
|
|
if (intval($arrRow['par_id_original'] ?? 0) <= 0) {
|
|
$intSkippedNoExternal++;
|
|
} else {
|
|
$intSkippedNoRace++;
|
|
}
|
|
continue;
|
|
}
|
|
$intEligible++;
|
|
$tabEligibleKeys[$arrPayload['external_id']] = true;
|
|
}
|
|
|
|
$arrCtFetch = fxChronotrackApiSyncFetchCtEntriesIndexed($intCtEventId);
|
|
if ($arrCtFetch['state'] !== 'ok') {
|
|
return array(
|
|
'state' => 'error',
|
|
'message' => $arrCtFetch['message'],
|
|
);
|
|
}
|
|
|
|
$tabCtEntries = $arrCtFetch['entries'];
|
|
$intCtTotal = count($tabCtEntries);
|
|
$intAlreadyInCt = 0;
|
|
$intToCreate = 0;
|
|
|
|
foreach ($tabEligibleKeys as $strExt => $blnTrue) {
|
|
if (isset($tabCtEntries[$strExt])) {
|
|
$intAlreadyInCt++;
|
|
} else {
|
|
$intToCreate++;
|
|
}
|
|
}
|
|
|
|
return array(
|
|
'state' => 'ok',
|
|
'ct_event_id' => $intCtEventId,
|
|
'ms1_total' => $intMs1Total,
|
|
'eligible' => $intEligible,
|
|
'skipped_no_race' => $intSkippedNoRace,
|
|
'skipped_no_external'=> $intSkippedNoExternal,
|
|
'ct_total' => $intCtTotal,
|
|
'already_in_ct' => $intAlreadyInCt,
|
|
'to_create' => $intToCreate,
|
|
'to_push' => $intEligible,
|
|
);
|
|
}
|
|
|
|
function fxChronotrackApiSyncPushEvent($intEveId) {
|
|
$arrPreview = fxChronotrackApiSyncBuildPreview($intEveId);
|
|
if ($arrPreview['state'] !== 'ok') {
|
|
return $arrPreview;
|
|
}
|
|
|
|
$intCtEventId = intval($arrPreview['ct_event_id']);
|
|
$arrRaceMap = fxChronotrackApiRaceMapGetForEvent($intEveId);
|
|
$tabParticipants = fxChronotrackApiSyncLoadMs1Participants($intEveId);
|
|
|
|
$tabBatch = array();
|
|
$tabMeta = array();
|
|
$intSkipped = 0;
|
|
|
|
foreach ($tabParticipants as $arrRow) {
|
|
$arrPayload = fxChronotrackApiSyncBuildEntryPayload($arrRow, $arrRaceMap);
|
|
if ($arrPayload === null) {
|
|
$intSkipped++;
|
|
continue;
|
|
}
|
|
$tabBatch[] = $arrPayload;
|
|
$tabMeta[] = array(
|
|
'par_id' => intval($arrRow['par_id']),
|
|
'external_id' => $arrPayload['external_id'],
|
|
);
|
|
}
|
|
|
|
if (count($tabBatch) === 0) {
|
|
return array(
|
|
'state' => 'error',
|
|
'message' => 'Aucun participant éligible (vérifiez le mapping des épreuves).',
|
|
);
|
|
}
|
|
|
|
$intOk = 0;
|
|
$intErr = 0;
|
|
$tabErrors = array();
|
|
$intBatchSize = MSIN_API_CHRONOTRACK_SYNC_BATCH_SIZE;
|
|
$intTotal = count($tabBatch);
|
|
|
|
for ($intOffset = 0; $intOffset < $intTotal; $intOffset += $intBatchSize) {
|
|
$tabSlice = array_slice($tabBatch, $intOffset, $intBatchSize);
|
|
$tabSliceMeta = array_slice($tabMeta, $intOffset, $intBatchSize);
|
|
|
|
$arrPost = fxChronotrackApiOAuthApiPost(
|
|
'event/' . $intCtEventId . '/entry',
|
|
array('event_entry' => $tabSlice)
|
|
);
|
|
|
|
if ($arrPost['state'] === 'ok') {
|
|
$intOk += count($tabSlice);
|
|
foreach ($tabSliceMeta as $arrM) {
|
|
fxChronotrackApiSyncLog(
|
|
$intEveId,
|
|
$arrM['par_id'],
|
|
'push_entry',
|
|
'ok',
|
|
'external_id=' . $arrM['external_id']
|
|
);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$strErrMsg = $arrPost['message'] ?? 'Erreur POST entry';
|
|
$intErr += count($tabSlice);
|
|
$tabErrors[] = $strErrMsg;
|
|
foreach ($tabSliceMeta as $arrM) {
|
|
fxChronotrackApiSyncLog(
|
|
$intEveId,
|
|
$arrM['par_id'],
|
|
'push_entry',
|
|
'error',
|
|
'external_id=' . $arrM['external_id'] . ' — ' . $strErrMsg
|
|
);
|
|
}
|
|
}
|
|
|
|
$strState = ($intErr === 0) ? 'ok' : (($intOk > 0) ? 'partial' : 'error');
|
|
|
|
return array(
|
|
'state' => $strState,
|
|
'message' => ($intErr === 0)
|
|
? ($intOk . ' participant(s) envoyé(s) vers ChronoTrack.')
|
|
: ($intOk . ' OK, ' . $intErr . ' erreur(s).'),
|
|
'pushed_ok' => $intOk,
|
|
'pushed_error' => $intErr,
|
|
'skipped' => $intSkipped,
|
|
'errors' => array_slice($tabErrors, 0, 5),
|
|
'preview' => $arrPreview,
|
|
);
|
|
}
|