This commit introduces a new function to classify blocked participants by type, improving the handling of synchronization errors related to missing or invalid data. The changes include updates to the participant classification logic, allowing for better reporting of issues such as duplicate bibs, missing external IDs, and unmapped races. Additionally, the push preview functionality is enhanced to display blocked participants clearly, improving user feedback during the synchronization process. These updates aim to streamline participant management and enhance the overall user experience within the ChronoTrack API.
737 lines
26 KiB
PHP
737 lines
26 KiB
PHP
<?php
|
|
/**
|
|
* MSIN API ChronoTrack — Phase 2a : lecture MS1, diff, push manuel entries.
|
|
*/
|
|
|
|
define('MSIN_API_CHRONOTRACK_SYNC_BATCH_SIZE', 10);
|
|
|
|
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') {
|
|
return 'F';
|
|
}
|
|
if ($strSexe === 'h' || $strSexe === 'm') {
|
|
return 'M';
|
|
}
|
|
if ($strSexe === '') {
|
|
return 'NOT SPECIFIED';
|
|
}
|
|
// TODO ChronoTrack : autres valeurs MS1 (ex. « n » non-binaire, « a » autre) — confirmer
|
|
// les options acceptées par l'API CT et ajouter le mapping ici avant transfert automatique.
|
|
return '';
|
|
}
|
|
|
|
function fxChronotrackApiSyncHasValidSex($strSexe) {
|
|
return fxChronotrackApiSyncNormalizeSex($strSexe) !== '';
|
|
}
|
|
|
|
function fxChronotrackApiSyncExtractBib(array $arrRow) {
|
|
$strBib = trim((string)($arrRow['no_bib'] ?? ''));
|
|
if ($strBib === '' || $strBib === '0') {
|
|
return '';
|
|
}
|
|
return $strBib;
|
|
}
|
|
|
|
function fxChronotrackApiSyncParticipantLabel(array $arrRow) {
|
|
return trim(trim((string)($arrRow['par_prenom'] ?? '')) . ' ' . trim((string)($arrRow['par_nom'] ?? '')));
|
|
}
|
|
|
|
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'] ?? '')),
|
|
);
|
|
}
|
|
|
|
function fxChronotrackApiSyncGroupBlockedByType(array $tabBlocked) {
|
|
$tabTypeLabels = array(
|
|
'duplicate_bib' => 'Dossard en double (MS1)',
|
|
'no_bib' => 'Dossard manquant',
|
|
'no_sex' => 'Sexe non reconnu',
|
|
'no_race' => 'Épreuve non mappée',
|
|
'no_external' => 'External ID manquant',
|
|
);
|
|
$tabGroups = array();
|
|
foreach ($tabBlocked as $arrItem) {
|
|
$strCode = (string)($arrItem['code'] ?? 'other');
|
|
if (!isset($tabGroups[$strCode])) {
|
|
$tabGroups[$strCode] = array(
|
|
'code' => $strCode,
|
|
'label' => isset($tabTypeLabels[$strCode]) ? $tabTypeLabels[$strCode] : $strCode,
|
|
'items' => array(),
|
|
);
|
|
}
|
|
$tabGroups[$strCode]['items'][] = $arrItem;
|
|
}
|
|
return array_values($tabGroups);
|
|
}
|
|
|
|
/**
|
|
* Classe les participants MS1 : transférables vs exclus (dossard, sexe, doublons MS1, mapping).
|
|
*/
|
|
function fxChronotrackApiSyncClassifyParticipants(array $tabParticipants, array $arrRaceMap, $intCtEventId = 0) {
|
|
$tabCandidates = array();
|
|
$tabBlocked = array();
|
|
$tabBibCounts = array();
|
|
|
|
foreach ($tabParticipants as $arrRow) {
|
|
$intExternalId = intval($arrRow['par_id_original'] ?? 0);
|
|
if ($intExternalId <= 0) {
|
|
$tabBlocked[] = array(
|
|
'code' => 'no_external',
|
|
'message' => 'par_id_original manquant',
|
|
'par_id' => intval($arrRow['par_id'] ?? 0),
|
|
'external_id' => '',
|
|
'name' => fxChronotrackApiSyncParticipantLabel($arrRow),
|
|
'no_bib' => fxChronotrackApiSyncExtractBib($arrRow),
|
|
'par_sexe' => trim((string)($arrRow['par_sexe'] ?? '')),
|
|
);
|
|
continue;
|
|
}
|
|
|
|
$intEprId = intval($arrRow['epr_id'] ?? 0);
|
|
$intCtRaceId = intval($arrRaceMap[$intEprId]['ct_race_id'] ?? 0);
|
|
if ($intCtRaceId <= 0) {
|
|
$arrSummary = fxChronotrackApiSyncParticipantSummary($arrRow);
|
|
$tabBlocked[] = array(
|
|
'code' => 'no_race',
|
|
'message' => 'Épreuve MS1 (epr_id ' . $intEprId . ') non mappée vers ChronoTrack',
|
|
) + $arrSummary;
|
|
continue;
|
|
}
|
|
|
|
$tabCandidates[] = $arrRow;
|
|
$strBib = fxChronotrackApiSyncExtractBib($arrRow);
|
|
if ($strBib !== '') {
|
|
if (!isset($tabBibCounts[$strBib])) {
|
|
$tabBibCounts[$strBib] = 0;
|
|
}
|
|
$tabBibCounts[$strBib]++;
|
|
}
|
|
}
|
|
|
|
$tabDuplicateBibs = array();
|
|
foreach ($tabBibCounts as $strBib => $intCount) {
|
|
if ($intCount > 1) {
|
|
$tabDuplicateBibs[$strBib] = true;
|
|
}
|
|
}
|
|
|
|
$tabTransferable = array();
|
|
$intBlockedNoBib = 0;
|
|
$intBlockedNoSex = 0;
|
|
$intBlockedDuplicateBib = 0;
|
|
|
|
foreach ($tabCandidates as $arrRow) {
|
|
$arrSummary = fxChronotrackApiSyncParticipantSummary($arrRow);
|
|
$strBib = $arrSummary['no_bib'];
|
|
|
|
if ($strBib !== '' && isset($tabDuplicateBibs[$strBib])) {
|
|
$intBlockedDuplicateBib++;
|
|
$tabBlocked[] = array(
|
|
'code' => 'duplicate_bib',
|
|
'message' => 'Dossard #' . $strBib . ' en double — corriger dans MS1',
|
|
'bib' => $strBib,
|
|
) + $arrSummary;
|
|
continue;
|
|
}
|
|
|
|
if ($strBib === '') {
|
|
$intBlockedNoBib++;
|
|
$tabBlocked[] = array(
|
|
'code' => 'no_bib',
|
|
'message' => 'Dossard manquant — corriger dans MS1',
|
|
) + $arrSummary;
|
|
continue;
|
|
}
|
|
|
|
if (!fxChronotrackApiSyncHasValidSex($arrRow['par_sexe'] ?? '')) {
|
|
$intBlockedNoSex++;
|
|
$strSexeMs1 = trim((string)($arrRow['par_sexe'] ?? ''));
|
|
$tabBlocked[] = array(
|
|
'code' => 'no_sex',
|
|
'message' => 'Sexe « ' . $strSexeMs1 . ' » — mapping CT à définir',
|
|
) + $arrSummary;
|
|
continue;
|
|
}
|
|
|
|
$arrPayload = fxChronotrackApiSyncBuildEntryPayload($arrRow, $arrRaceMap, $intCtEventId);
|
|
if ($arrPayload === null) {
|
|
continue;
|
|
}
|
|
|
|
$tabTransferable[] = array(
|
|
'row' => $arrRow,
|
|
'payload' => $arrPayload,
|
|
'summary' => $arrSummary,
|
|
);
|
|
}
|
|
|
|
$intBlockedNoRace = 0;
|
|
$intBlockedNoExternal = 0;
|
|
foreach ($tabBlocked as $arrItem) {
|
|
if (($arrItem['code'] ?? '') === 'no_race') {
|
|
$intBlockedNoRace++;
|
|
} elseif (($arrItem['code'] ?? '') === 'no_external') {
|
|
$intBlockedNoExternal++;
|
|
}
|
|
}
|
|
|
|
return array(
|
|
'transferable' => $tabTransferable,
|
|
'blocked' => $tabBlocked,
|
|
'blocked_by_type' => fxChronotrackApiSyncGroupBlockedByType($tabBlocked),
|
|
'blocked_count' => count($tabBlocked),
|
|
'skipped_no_race' => $intBlockedNoRace,
|
|
'skipped_no_external' => $intBlockedNoExternal,
|
|
'blocked_no_bib' => $intBlockedNoBib,
|
|
'blocked_no_sex' => $intBlockedNoSex,
|
|
'blocked_duplicate_bib' => $intBlockedDuplicateBib,
|
|
'duplicate_bib_numbers' => array_keys($tabDuplicateBibs),
|
|
// Alias rétrocompat (compteurs)
|
|
'anomalies' => $tabBlocked,
|
|
'anomaly_no_bib' => $intBlockedNoBib,
|
|
'anomaly_no_sex' => $intBlockedNoSex,
|
|
'anomaly_duplicate_bib' => $intBlockedDuplicateBib,
|
|
);
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
$strCountry = strtoupper(trim((string)($arrRow['country_iso3'] ?? '')));
|
|
if ($strCountry === '') {
|
|
$strCountry = fxChronotrackApiSyncIso2ToIso3($arrRow['country_iso2'] ?? '');
|
|
}
|
|
$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,
|
|
'bib' => $strBib,
|
|
);
|
|
|
|
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;
|
|
}
|
|
|
|
$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 fxChronotrackApiSyncPostEntries($intCtEventId, array $tabEntries) {
|
|
$intCtEventId = intval($intCtEventId);
|
|
if ($intCtEventId <= 0 || count($tabEntries) === 0) {
|
|
return array('state' => 'error', 'message' => 'Aucune entry à envoyer');
|
|
}
|
|
|
|
// Doc CT : POST body = tableau JSON d'entités entry (pas de wrapper event_entry).
|
|
$arrPost = fxChronotrackApiOAuthApiPost('event/' . $intCtEventId . '/entry', $tabEntries);
|
|
if ($arrPost['state'] === 'ok') {
|
|
$arrPost['body_format'] = 'array';
|
|
return $arrPost;
|
|
}
|
|
|
|
$arrPostWrap = fxChronotrackApiOAuthApiPost(
|
|
'event/' . $intCtEventId . '/entry',
|
|
array('event_entry' => $tabEntries)
|
|
);
|
|
if ($arrPostWrap['state'] === 'ok') {
|
|
$arrPostWrap['body_format'] = 'event_entry';
|
|
return $arrPostWrap;
|
|
}
|
|
|
|
$arrPost['body_format'] = 'array';
|
|
$arrPost['fallback_error'] = $arrPostWrap['message'] ?? '';
|
|
return $arrPost;
|
|
}
|
|
|
|
function fxChronotrackApiSyncProbePush($intEveId) {
|
|
$arrConfig = fxChronotrackApiConfigGet($intEveId);
|
|
if ($arrConfig === null) {
|
|
return array('state' => 'error', 'message' => 'Événement non lié à ChronoTrack');
|
|
}
|
|
|
|
$intCtEventId = intval($arrConfig['ct_event_id']);
|
|
$arrRaceMap = fxChronotrackApiRaceMapGetForEvent($intEveId);
|
|
$tabParticipants = fxChronotrackApiSyncLoadMs1Participants($intEveId);
|
|
$arrClass = fxChronotrackApiSyncClassifyParticipants($tabParticipants, $arrRaceMap, $intCtEventId);
|
|
|
|
if (count($arrClass['transferable']) === 0) {
|
|
return array('state' => 'error', 'message' => 'Aucun participant transférable pour la sonde (voir anomalies).');
|
|
}
|
|
|
|
$arrFirst = $arrClass['transferable'][0];
|
|
$arrPayload = $arrFirst['payload'];
|
|
$arrRow = $arrFirst['row'];
|
|
|
|
$arrPost = fxChronotrackApiSyncPostEntries($intCtEventId, array($arrPayload));
|
|
|
|
return array(
|
|
'state' => $arrPost['state'],
|
|
'message' => $arrPost['message'] ?? '',
|
|
'ct_event_id' => $intCtEventId,
|
|
'par_id' => intval($arrRow['par_id'] ?? 0),
|
|
'payload' => $arrPayload,
|
|
'body_format' => $arrPost['body_format'] ?? '',
|
|
'http_code' => intval($arrPost['http']['http_code'] ?? 0),
|
|
'response_body' => substr(trim((string)($arrPost['http']['body'] ?? '')), 0, 2000),
|
|
'fallback_error'=> $arrPost['fallback_error'] ?? '',
|
|
);
|
|
}
|
|
|
|
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);
|
|
$arrClass = fxChronotrackApiSyncClassifyParticipants($tabParticipants, $arrRaceMap, $intCtEventId);
|
|
|
|
$intMs1Total = count($tabParticipants);
|
|
$intTransferable = count($arrClass['transferable']);
|
|
$tabEligibleKeys = array();
|
|
foreach ($arrClass['transferable'] as $arrItem) {
|
|
$tabEligibleKeys[$arrItem['payload']['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,
|
|
'transferable' => $intTransferable,
|
|
'blocked_count' => $arrClass['blocked_count'],
|
|
'blocked_by_type' => $arrClass['blocked_by_type'],
|
|
'blocked' => $arrClass['blocked'],
|
|
'eligible' => $intTransferable,
|
|
'skipped_no_race' => $arrClass['skipped_no_race'],
|
|
'skipped_no_external' => $arrClass['skipped_no_external'],
|
|
'blocked_no_bib' => $arrClass['blocked_no_bib'],
|
|
'blocked_no_sex' => $arrClass['blocked_no_sex'],
|
|
'blocked_duplicate_bib' => $arrClass['blocked_duplicate_bib'],
|
|
'anomaly_no_bib' => $arrClass['blocked_no_bib'],
|
|
'anomaly_no_sex' => $arrClass['blocked_no_sex'],
|
|
'anomaly_duplicate_bib' => $arrClass['blocked_duplicate_bib'],
|
|
'duplicate_bib_numbers' => $arrClass['duplicate_bib_numbers'],
|
|
'anomalies' => $arrClass['blocked'],
|
|
'ct_total' => $intCtTotal,
|
|
'already_in_ct' => $intAlreadyInCt,
|
|
'to_create' => $intToCreate,
|
|
'to_push' => $intTransferable,
|
|
);
|
|
}
|
|
|
|
function fxChronotrackApiSyncPushEvent($intEveId) {
|
|
$arrPreview = fxChronotrackApiSyncBuildPreview($intEveId);
|
|
if ($arrPreview['state'] !== 'ok') {
|
|
return $arrPreview;
|
|
}
|
|
|
|
$intCtEventId = intval($arrPreview['ct_event_id']);
|
|
$arrRaceMap = fxChronotrackApiRaceMapGetForEvent($intEveId);
|
|
$tabParticipants = fxChronotrackApiSyncLoadMs1Participants($intEveId);
|
|
$arrClass = fxChronotrackApiSyncClassifyParticipants($tabParticipants, $arrRaceMap, $intCtEventId);
|
|
|
|
foreach ($arrClass['blocked'] as $arrBlocked) {
|
|
fxChronotrackApiSyncLog(
|
|
$intEveId,
|
|
intval($arrBlocked['par_id'] ?? 0),
|
|
'push_skip',
|
|
'error',
|
|
($arrBlocked['code'] ?? 'blocked') . ' — external_id=' . ($arrBlocked['external_id'] ?? '')
|
|
. ' — ' . ($arrBlocked['message'] ?? '')
|
|
);
|
|
}
|
|
|
|
$tabBatch = array();
|
|
$tabMeta = array();
|
|
foreach ($arrClass['transferable'] as $arrItem) {
|
|
$tabBatch[] = $arrItem['payload'];
|
|
$tabMeta[] = array(
|
|
'par_id' => intval($arrItem['row']['par_id'] ?? 0),
|
|
'external_id' => $arrItem['payload']['external_id'],
|
|
);
|
|
}
|
|
|
|
$intSkipped = $arrClass['blocked_count'];
|
|
$tabBlockedReport = $arrClass['blocked'];
|
|
|
|
if (count($tabBatch) === 0) {
|
|
return array(
|
|
'state' => 'error',
|
|
'message' => 'Aucun participant transférable.'
|
|
. ($intSkipped > 0 ? ' ' . $intSkipped . ' exclus — voir le détail avant le push.' : ''),
|
|
'blocked' => $tabBlockedReport,
|
|
'blocked_by_type' => $arrClass['blocked_by_type'],
|
|
'blocked_count' => $intSkipped,
|
|
'preview' => $arrPreview,
|
|
);
|
|
}
|
|
|
|
$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 = fxChronotrackApiSyncPostEntries($intCtEventId, $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';
|
|
if (!empty($arrPost['fallback_error'])) {
|
|
$strErrMsg .= ' | wrapper: ' . $arrPost['fallback_error'];
|
|
}
|
|
|
|
foreach ($tabSlice as $intIdx => $arrEntry) {
|
|
$arrM = $tabSliceMeta[$intIdx];
|
|
$arrOne = fxChronotrackApiSyncPostEntries($intCtEventId, array($arrEntry));
|
|
if ($arrOne['state'] === 'ok') {
|
|
$intOk++;
|
|
fxChronotrackApiSyncLog(
|
|
$intEveId,
|
|
$arrM['par_id'],
|
|
'push_entry',
|
|
'ok',
|
|
'external_id=' . $arrM['external_id']
|
|
);
|
|
continue;
|
|
}
|
|
|
|
$intErr++;
|
|
$strOneErr = $arrOne['message'] ?? $strErrMsg;
|
|
if (count($tabErrors) < 8) {
|
|
$tabErrors[] = 'external_id=' . $arrM['external_id'] . ' — ' . $strOneErr;
|
|
}
|
|
fxChronotrackApiSyncLog(
|
|
$intEveId,
|
|
$arrM['par_id'],
|
|
'push_entry',
|
|
'error',
|
|
'external_id=' . $arrM['external_id'] . ' — ' . $strOneErr
|
|
);
|
|
}
|
|
}
|
|
|
|
$strState = ($intErr === 0) ? 'ok' : (($intOk > 0) ? 'partial' : 'error');
|
|
$intBlockedCount = $arrClass['blocked_count'];
|
|
|
|
$strMessage = ($intErr === 0)
|
|
? ($intOk . ' participant(s) envoyé(s) vers ChronoTrack.')
|
|
: ($intOk . ' OK, ' . $intErr . ' erreur(s) API.');
|
|
|
|
return array(
|
|
'state' => $strState,
|
|
'message' => $strMessage,
|
|
'pushed_ok' => $intOk,
|
|
'pushed_error' => $intErr,
|
|
'skipped' => $intBlockedCount,
|
|
'blocked_count' => $intBlockedCount,
|
|
'errors' => array_slice($tabErrors, 0, 20),
|
|
'preview' => $arrPreview,
|
|
);
|
|
}
|