This commit introduces new functions for managing entry withdrawal within the ChronoTrack API, including the ability to probe and withdraw entries based on their status. The admin interface is updated to include buttons for testing withdrawal actions, providing clearer user feedback. Additionally, the synchronization logic is enhanced to summarize entry statuses and handle batch withdrawals more effectively. These changes aim to improve the overall management of entries and enhance user experience in the API.
1191 lines
43 KiB
PHP
1191 lines
43 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 === '' || $strSexe === 'n') {
|
|
return 'NOT SPECIFIED';
|
|
}
|
|
// TODO ChronoTrack : « a » (autre / non-binaire selon config épreuve) — confirmer option CT.
|
|
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 fxChronotrackApiSyncIsTeamRow(array $arrRow) {
|
|
return intval($arrRow['pec_equipe'] ?? 0) === 1 || intval($arrRow['par_equipe'] ?? 0) === 1;
|
|
}
|
|
|
|
function fxChronotrackApiSyncFormatRoleLabel($intRolId) {
|
|
switch (intval($intRolId)) {
|
|
case 1:
|
|
return 'Capitaine';
|
|
case 2:
|
|
return 'Membre';
|
|
default:
|
|
return $intRolId > 0 ? ('Rôle ' . intval($intRolId)) : '';
|
|
}
|
|
}
|
|
|
|
function fxChronotrackApiSyncFormatTeamBlockMessage(array $arrRow) {
|
|
$arrParts = array('Inscription équipe — transfert individuel seulement (traitement équipe à venir)');
|
|
$strTeamName = trim((string)($arrRow['pec_nom_equipe'] ?? $arrRow['par_nom_equipe'] ?? ''));
|
|
if ($strTeamName !== '') {
|
|
$arrParts[] = '« ' . $strTeamName . ' »';
|
|
}
|
|
$strNoEquipe = trim((string)($arrRow['no_equipe'] ?? ''));
|
|
if ($strNoEquipe !== '' && $strNoEquipe !== '0') {
|
|
$arrParts[] = 'no équipe #' . $strNoEquipe;
|
|
}
|
|
return implode(' · ', $arrParts);
|
|
}
|
|
|
|
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' => trim((string)($arrRow['pec_nom_equipe'] ?? $arrRow['par_nom_equipe'] ?? '')),
|
|
'no_equipe' => trim((string)($arrRow['no_equipe'] ?? '')),
|
|
'role_label' => fxChronotrackApiSyncFormatRoleLabel($arrRow['rol_id'] ?? 0),
|
|
);
|
|
}
|
|
|
|
function fxChronotrackApiSyncGroupBlockedByType(array $tabBlocked) {
|
|
$tabTypeLabels = array(
|
|
'team' => 'Inscriptions équipe',
|
|
'duplicate_bib' => 'Dossard en double (individuel)',
|
|
'no_bib' => 'Dossard manquant',
|
|
'no_sex' => 'Sexe non reconnu',
|
|
'no_race' => 'Épreuve non mappée',
|
|
'no_external' => 'External ID manquant',
|
|
);
|
|
$tabTypeOrder = array('team', 'duplicate_bib', 'no_bib', 'no_sex', 'no_race', 'no_external');
|
|
$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;
|
|
}
|
|
|
|
$tabOrdered = array();
|
|
foreach ($tabTypeOrder as $strCode) {
|
|
if (isset($tabGroups[$strCode])) {
|
|
$tabOrdered[] = $tabGroups[$strCode];
|
|
unset($tabGroups[$strCode]);
|
|
}
|
|
}
|
|
foreach ($tabGroups as $arrGroup) {
|
|
$tabOrdered[] = $arrGroup;
|
|
}
|
|
return $tabOrdered;
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
$intBlockedTeam = 0;
|
|
$intBlockedNoBib = 0;
|
|
$intBlockedNoSex = 0;
|
|
$intBlockedDuplicateBib = 0;
|
|
|
|
foreach ($tabCandidates as $arrRow) {
|
|
$arrSummary = fxChronotrackApiSyncParticipantSummary($arrRow);
|
|
|
|
if (fxChronotrackApiSyncIsTeamRow($arrRow)) {
|
|
$intBlockedTeam++;
|
|
$tabBlocked[] = array(
|
|
'code' => 'team',
|
|
'message' => fxChronotrackApiSyncFormatTeamBlockMessage($arrRow),
|
|
) + $arrSummary;
|
|
continue;
|
|
}
|
|
|
|
$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,
|
|
'blocked_team' => $intBlockedTeam,
|
|
'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.pec_id, p.rol_id, p.par_equipe, p.par_nom_equipe,"
|
|
. " 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,"
|
|
. " ec.pec_equipe, ec.no_equipe, ec.pec_nom_equipe,"
|
|
. " (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'] ?? '',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Liste toutes les entries CT (pagination), dédupliquées par entry_id.
|
|
* Ne filtre pas sur external_id — requis pour le wipe et le décompte réel.
|
|
*/
|
|
function fxChronotrackApiSyncFetchAllCtEntryEntities($intCtEventId) {
|
|
$intCtEventId = intval($intCtEventId);
|
|
$tabAll = array();
|
|
$tabSeenEntryIds = array();
|
|
$intPage = 1;
|
|
$intPageSize = 100;
|
|
$intMaxPages = 500;
|
|
|
|
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',
|
|
'entities' => $tabAll,
|
|
);
|
|
}
|
|
|
|
$arrEntities = fxChronotrackApiNormalizeEntityList($arrApi['json'], 'entry');
|
|
if (count($arrEntities) === 0) {
|
|
break;
|
|
}
|
|
|
|
foreach ($arrEntities as $arrEntity) {
|
|
if (!is_array($arrEntity)) {
|
|
continue;
|
|
}
|
|
$strEntryId = fxChronotrackApiEntityId($arrEntity);
|
|
if ($strEntryId === '' || isset($tabSeenEntryIds[$strEntryId])) {
|
|
continue;
|
|
}
|
|
$tabSeenEntryIds[$strEntryId] = true;
|
|
$tabAll[] = $arrEntity;
|
|
}
|
|
|
|
if (count($arrEntities) < $intPageSize) {
|
|
break;
|
|
}
|
|
$intPage++;
|
|
}
|
|
|
|
return array(
|
|
'state' => 'ok',
|
|
'message' => '',
|
|
'entities' => $tabAll,
|
|
'count' => count($tabAll),
|
|
);
|
|
}
|
|
|
|
function fxChronotrackApiSyncFetchCtEntriesIndexed($intCtEventId) {
|
|
$arrFetchAll = fxChronotrackApiSyncFetchAllCtEntryEntities($intCtEventId);
|
|
if ($arrFetchAll['state'] !== 'ok') {
|
|
return array(
|
|
'state' => 'error',
|
|
'message' => $arrFetchAll['message'] ?? 'Erreur lecture entries CT',
|
|
'entries' => array(),
|
|
);
|
|
}
|
|
|
|
$tabIndexed = array();
|
|
foreach ($arrFetchAll['entities'] as $arrEntity) {
|
|
if (!is_array($arrEntity)) {
|
|
continue;
|
|
}
|
|
$strExt = fxChronotrackApiEntityExternalId($arrEntity);
|
|
if ($strExt === '') {
|
|
continue;
|
|
}
|
|
$tabIndexed[$strExt] = $arrEntity;
|
|
}
|
|
|
|
return array(
|
|
'state' => 'ok',
|
|
'message' => '',
|
|
'entries' => $tabIndexed,
|
|
'total_entry_count' => intval($arrFetchAll['count'] ?? count($arrFetchAll['entities'])),
|
|
'without_external_id'=> max(0, intval($arrFetchAll['count'] ?? 0) - count($tabIndexed)),
|
|
);
|
|
}
|
|
|
|
function fxChronotrackApiSyncFetchCtEntryRows($intCtEventId) {
|
|
$arrFetch = fxChronotrackApiSyncFetchAllCtEntryEntities($intCtEventId);
|
|
if ($arrFetch['state'] !== 'ok') {
|
|
return $arrFetch;
|
|
}
|
|
|
|
$tabRows = array();
|
|
foreach ($arrFetch['entities'] as $arrEntity) {
|
|
if (!is_array($arrEntity)) {
|
|
continue;
|
|
}
|
|
$strEntryId = fxChronotrackApiEntityId($arrEntity);
|
|
if ($strEntryId === '') {
|
|
continue;
|
|
}
|
|
$tabRows[] = array(
|
|
'entry_id' => $strEntryId,
|
|
'external_id' => fxChronotrackApiEntityExternalId($arrEntity),
|
|
'entity' => $arrEntity,
|
|
);
|
|
}
|
|
|
|
return array(
|
|
'state' => 'ok',
|
|
'message' => '',
|
|
'rows' => $tabRows,
|
|
'count' => count($tabRows),
|
|
);
|
|
}
|
|
|
|
function fxChronotrackApiSyncEntryRowStatus(array $arrRow) {
|
|
$arrEntity = $arrRow['entity'] ?? array();
|
|
if (!is_array($arrEntity)) {
|
|
return '';
|
|
}
|
|
return strtoupper(trim(fxChronotrackApiRaceField($arrEntity, array('entry_status', 'status'))));
|
|
}
|
|
|
|
function fxChronotrackApiSyncIsCtEntryWithdrawn(array $arrRow) {
|
|
$strStatus = fxChronotrackApiSyncEntryRowStatus($arrRow);
|
|
return in_array($strStatus, array('WITHDRAWN', 'WD', 'CANCELLED', 'CANCEL', 'REFUND'), true);
|
|
}
|
|
|
|
function fxChronotrackApiSyncSummarizeCtEntryRows(array $tabRows) {
|
|
$intWithdrawn = 0;
|
|
foreach ($tabRows as $arrRow) {
|
|
if (fxChronotrackApiSyncIsCtEntryWithdrawn($arrRow)) {
|
|
$intWithdrawn++;
|
|
}
|
|
}
|
|
$intTotal = count($tabRows);
|
|
return array(
|
|
'total' => $intTotal,
|
|
'active' => max(0, $intTotal - $intWithdrawn),
|
|
'withdrawn' => $intWithdrawn,
|
|
);
|
|
}
|
|
|
|
function fxChronotrackApiSyncBuildWithdrawPayload(array $arrRow, $strStatus = 'WITHDRAWN') {
|
|
$arrEntity = $arrRow['entity'] ?? array();
|
|
$arrPayload = array(
|
|
'entry_status' => strtoupper(trim((string)$strStatus)),
|
|
);
|
|
|
|
$strEntryId = trim((string)($arrRow['entry_id'] ?? ''));
|
|
if ($strEntryId !== '') {
|
|
$arrPayload['entry_id'] = $strEntryId;
|
|
}
|
|
|
|
$strExternalId = trim((string)($arrRow['external_id'] ?? ''));
|
|
if ($strExternalId !== '') {
|
|
$arrPayload['external_id'] = $strExternalId;
|
|
}
|
|
|
|
if (is_array($arrEntity)) {
|
|
$intRaceId = intval(fxChronotrackApiRaceField($arrEntity, array('race_id', 'event_race_id')));
|
|
if ($intRaceId > 0) {
|
|
$arrPayload['race_id'] = $intRaceId;
|
|
}
|
|
$strBib = fxChronotrackApiRaceField($arrEntity, array('bib', 'bib_number', 'entry_bib'));
|
|
if ($strBib !== '') {
|
|
$arrPayload['bib'] = $strBib;
|
|
}
|
|
}
|
|
|
|
return $arrPayload;
|
|
}
|
|
|
|
function fxChronotrackApiSyncFindCtEntryRowById($intCtEventId, $strEntryId) {
|
|
$arrFetch = fxChronotrackApiSyncFetchCtEntryRows($intCtEventId);
|
|
if ($arrFetch['state'] !== 'ok') {
|
|
return null;
|
|
}
|
|
foreach ($arrFetch['rows'] as $arrRow) {
|
|
if ((string)$arrRow['entry_id'] === (string)$strEntryId) {
|
|
return $arrRow;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function fxChronotrackApiSyncWithdrawEntriesBatch($intCtEventId, array $tabPayloads) {
|
|
if (count($tabPayloads) === 0) {
|
|
return array('state' => 'error', 'message' => 'Lot WITHDRAWN vide');
|
|
}
|
|
return fxChronotrackApiSyncPostEntries($intCtEventId, $tabPayloads);
|
|
}
|
|
|
|
function fxChronotrackApiSyncProbeWithdraw($intEveId) {
|
|
$arrConfig = fxChronotrackApiConfigGet($intEveId);
|
|
if ($arrConfig === null) {
|
|
return array('state' => 'error', 'message' => 'Événement non lié à ChronoTrack');
|
|
}
|
|
|
|
$intCtEventId = intval($arrConfig['ct_event_id']);
|
|
$arrFetch = fxChronotrackApiSyncFetchCtEntryRows($intCtEventId);
|
|
if ($arrFetch['state'] !== 'ok') {
|
|
return $arrFetch;
|
|
}
|
|
|
|
$arrTarget = null;
|
|
foreach ($arrFetch['rows'] as $arrRow) {
|
|
if (!fxChronotrackApiSyncIsCtEntryWithdrawn($arrRow)) {
|
|
$arrTarget = $arrRow;
|
|
break;
|
|
}
|
|
}
|
|
if ($arrTarget === null) {
|
|
return array('state' => 'warning', 'message' => 'Aucune entry active à retirer (toutes déjà WITHDRAWN ?).');
|
|
}
|
|
|
|
$strEntryId = (string)$arrTarget['entry_id'];
|
|
$strStatusBefore = fxChronotrackApiSyncEntryRowStatus($arrTarget);
|
|
$arrPayload = fxChronotrackApiSyncBuildWithdrawPayload($arrTarget, 'WITHDRAWN');
|
|
$arrPost = fxChronotrackApiSyncWithdrawEntriesBatch($intCtEventId, array($arrPayload));
|
|
|
|
$arrRet = array(
|
|
'state' => $arrPost['state'],
|
|
'message' => $arrPost['message'] ?? '',
|
|
'ct_event_id' => $intCtEventId,
|
|
'entry_id' => $strEntryId,
|
|
'external_id' => $arrTarget['external_id'] ?? '',
|
|
'status_before' => $strStatusBefore,
|
|
'payload' => $arrPayload,
|
|
'http_code' => intval($arrPost['http']['http_code'] ?? 0),
|
|
'response_body' => substr(trim((string)($arrPost['http']['body'] ?? '')), 0, 2000),
|
|
'method' => 'POST event/{id}/entry + entry_status=WITHDRAWN',
|
|
);
|
|
|
|
$arrAfter = fxChronotrackApiSyncFindCtEntryRowById($intCtEventId, $strEntryId);
|
|
if ($arrAfter !== null) {
|
|
$strStatusAfter = fxChronotrackApiSyncEntryRowStatus($arrAfter);
|
|
$arrRet['status_after'] = $strStatusAfter;
|
|
if ($arrPost['state'] === 'ok' && fxChronotrackApiSyncIsCtEntryWithdrawn($arrAfter)) {
|
|
$arrRet['state'] = 'ok';
|
|
$arrRet['message'] = 'Entry ' . $strEntryId . ' retirée (statut ' . $strStatusBefore
|
|
. ' → ' . $strStatusAfter . ') via POST WITHDRAWN.';
|
|
} elseif ($arrPost['state'] === 'ok') {
|
|
$arrRet['state'] = 'warning';
|
|
$arrRet['message'] = 'POST HTTP OK mais statut CT inchangé (' . $strStatusAfter
|
|
. '). Vérifier le nom du champ entry_status côté CT.';
|
|
}
|
|
}
|
|
|
|
return $arrRet;
|
|
}
|
|
|
|
function fxChronotrackApiSyncWithdrawCtEvent($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']);
|
|
$arrFetch = fxChronotrackApiSyncFetchCtEntryRows($intCtEventId);
|
|
if ($arrFetch['state'] !== 'ok') {
|
|
return $arrFetch;
|
|
}
|
|
|
|
$arrSummaryBefore = fxChronotrackApiSyncSummarizeCtEntryRows($arrFetch['rows']);
|
|
$tabActiveRows = array();
|
|
foreach ($arrFetch['rows'] as $arrRow) {
|
|
if (!fxChronotrackApiSyncIsCtEntryWithdrawn($arrRow)) {
|
|
$tabActiveRows[] = $arrRow;
|
|
}
|
|
}
|
|
|
|
if (count($tabActiveRows) === 0) {
|
|
return array(
|
|
'state' => 'warning',
|
|
'message' => 'Aucune entry active sur CT #' . $intCtEventId
|
|
. ' (' . $arrSummaryBefore['withdrawn'] . ' déjà WITHDRAWN sur '
|
|
. $arrSummaryBefore['total'] . '). MS1 intact.',
|
|
'ct_event_id' => $intCtEventId,
|
|
'found_before' => $arrSummaryBefore['total'],
|
|
'active_before' => 0,
|
|
'withdrawn_ok' => 0,
|
|
'withdrawn_error' => 0,
|
|
'active_after' => 0,
|
|
'remaining_after' => $arrSummaryBefore['total'],
|
|
'wipe_method' => 'withdraw_post',
|
|
'errors' => array(),
|
|
);
|
|
}
|
|
|
|
$arrProbe = fxChronotrackApiSyncProbeWithdraw($intEveId);
|
|
if ($arrProbe['state'] !== 'ok' && $arrProbe['state'] !== 'warning') {
|
|
return array(
|
|
'state' => 'error',
|
|
'message' => 'Impossible de retirer les entries via POST WITHDRAWN (sonde entry '
|
|
. ($arrProbe['entry_id'] ?? '?') . ') : ' . ($arrProbe['message'] ?? 'erreur'),
|
|
'ct_event_id' => $intCtEventId,
|
|
'found_before' => $arrSummaryBefore['total'],
|
|
'active_before' => count($tabActiveRows),
|
|
'withdrawn_ok' => 0,
|
|
'withdrawn_error' => count($tabActiveRows),
|
|
'active_after' => count($tabActiveRows),
|
|
'remaining_after' => $arrSummaryBefore['total'],
|
|
'wipe_method' => 'withdraw_post',
|
|
'withdraw_probe' => $arrProbe,
|
|
'errors' => array($arrProbe['message'] ?? 'Erreur sonde WITHDRAWN'),
|
|
);
|
|
}
|
|
|
|
$intOk = ($arrProbe['state'] === 'ok') ? 1 : 0;
|
|
$intErr = 0;
|
|
$tabErrors = array();
|
|
$intBatchSize = MSIN_API_CHRONOTRACK_SYNC_BATCH_SIZE;
|
|
$intStartIdx = ($arrProbe['state'] === 'ok') ? 1 : 0;
|
|
|
|
for ($intIdx = $intStartIdx; $intIdx < count($tabActiveRows); $intIdx += $intBatchSize) {
|
|
$tabBatch = array();
|
|
$tabBatchMeta = array();
|
|
for ($intJ = $intIdx; $intJ < min($intIdx + $intBatchSize, count($tabActiveRows)); $intJ++) {
|
|
$arrRow = $tabActiveRows[$intJ];
|
|
$tabBatch[] = fxChronotrackApiSyncBuildWithdrawPayload($arrRow, 'WITHDRAWN');
|
|
$tabBatchMeta[] = $arrRow;
|
|
}
|
|
|
|
$arrPost = fxChronotrackApiSyncWithdrawEntriesBatch($intCtEventId, $tabBatch);
|
|
if ($arrPost['state'] === 'ok') {
|
|
$intOk += count($tabBatch);
|
|
foreach ($tabBatchMeta as $arrRow) {
|
|
fxChronotrackApiSyncLog(
|
|
$intEveId,
|
|
0,
|
|
'ct_withdraw_entry',
|
|
'ok',
|
|
'entry_id=' . $arrRow['entry_id'] . ' external_id=' . ($arrRow['external_id'] ?? '')
|
|
);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
$intErr += count($tabBatch);
|
|
$strMsg = $arrPost['message'] ?? 'Erreur POST WITHDRAWN';
|
|
if (count($tabErrors) < 10) {
|
|
$tabErrors[] = 'lot ' . ($intIdx + 1) . '-' . min($intIdx + $intBatchSize, count($tabActiveRows))
|
|
. ' — ' . $strMsg;
|
|
}
|
|
foreach ($tabBatchMeta as $arrRow) {
|
|
fxChronotrackApiSyncLog(
|
|
$intEveId,
|
|
0,
|
|
'ct_withdraw_entry',
|
|
'error',
|
|
'entry_id=' . $arrRow['entry_id'] . ' — ' . $strMsg
|
|
);
|
|
}
|
|
}
|
|
|
|
$arrFetchAfter = fxChronotrackApiSyncFetchCtEntryRows($intCtEventId);
|
|
$arrSummaryAfter = ($arrFetchAfter['state'] === 'ok')
|
|
? fxChronotrackApiSyncSummarizeCtEntryRows($arrFetchAfter['rows'])
|
|
: array('total' => -1, 'active' => -1, 'withdrawn' => -1);
|
|
|
|
$intActiveAfter = intval($arrSummaryAfter['active']);
|
|
if ($intActiveAfter === 0 && $intErr === 0) {
|
|
$strState = 'ok';
|
|
$strMessage = $intOk . ' entry(s) retirée(s) (WITHDRAWN) sur ChronoTrack Live (event #' . $intCtEventId . ').';
|
|
$strMessage .= ' Actives restantes : 0. MS1 intact — un push remettra CONF.';
|
|
} elseif ($intOk > 0) {
|
|
$strState = 'partial';
|
|
$strMessage = $intOk . ' retirée(s), ' . $intErr . ' erreur(s).';
|
|
if ($intActiveAfter >= 0) {
|
|
$strMessage .= ' Actives restantes : ' . $intActiveAfter . '.';
|
|
}
|
|
} else {
|
|
$strState = 'error';
|
|
$strMessage = 'Échec retrait WITHDRAWN (' . $intErr . ' erreur(s)).';
|
|
}
|
|
|
|
return array(
|
|
'state' => $strState,
|
|
'message' => $strMessage,
|
|
'ct_event_id' => $intCtEventId,
|
|
'found_before' => $arrSummaryBefore['total'],
|
|
'active_before' => count($tabActiveRows),
|
|
'withdrawn_ok' => $intOk,
|
|
'withdrawn_error' => $intErr,
|
|
'active_after' => $intActiveAfter,
|
|
'remaining_after' => intval($arrSummaryAfter['total']),
|
|
'wipe_method' => 'withdraw_post',
|
|
'errors' => $tabErrors,
|
|
);
|
|
}
|
|
|
|
function fxChronotrackApiSyncProbeDelete($intEveId) {
|
|
$arrConfig = fxChronotrackApiConfigGet($intEveId);
|
|
if ($arrConfig === null) {
|
|
return array('state' => 'error', 'message' => 'Événement non lié à ChronoTrack');
|
|
}
|
|
|
|
$intCtEventId = intval($arrConfig['ct_event_id']);
|
|
$arrFetch = fxChronotrackApiSyncFetchCtEntryRows($intCtEventId);
|
|
if ($arrFetch['state'] !== 'ok') {
|
|
return $arrFetch;
|
|
}
|
|
if (count($arrFetch['rows']) === 0) {
|
|
return array('state' => 'warning', 'message' => 'Aucune entry CT à tester (GET event/' . $intCtEventId . '/entry).');
|
|
}
|
|
|
|
$strEntryId = (string)$arrFetch['rows'][0]['entry_id'];
|
|
$arrProbe = fxChronotrackApiOAuthApiDeleteEntryProbe($intCtEventId, $strEntryId);
|
|
$arrProbe['external_id'] = $arrFetch['rows'][0]['external_id'] ?? '';
|
|
return $arrProbe;
|
|
}
|
|
|
|
function fxChronotrackApiSyncWipeCtEvent($intEveId) {
|
|
return fxChronotrackApiSyncWithdrawCtEvent($intEveId);
|
|
}
|
|
|
|
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 = intval($arrCtFetch['total_entry_count'] ?? count($tabCtEntries));
|
|
$intCtWithoutExt = intval($arrCtFetch['without_external_id'] ?? 0);
|
|
$arrCtStats = array('total' => $intCtTotal, 'active' => $intCtTotal, 'withdrawn' => 0);
|
|
$arrCtRowsFetch = fxChronotrackApiSyncFetchCtEntryRows($intCtEventId);
|
|
if ($arrCtRowsFetch['state'] === 'ok') {
|
|
$arrCtStats = fxChronotrackApiSyncSummarizeCtEntryRows($arrCtRowsFetch['rows']);
|
|
}
|
|
$intCtActive = intval($arrCtStats['active']);
|
|
$intAlreadyInCt = 0;
|
|
$intToCreate = 0;
|
|
|
|
foreach ($tabEligibleKeys as $strExt => $blnTrue) {
|
|
if (isset($tabCtEntries[$strExt])) {
|
|
$intAlreadyInCt++;
|
|
} else {
|
|
$intToCreate++;
|
|
}
|
|
}
|
|
|
|
$intCtOrphans = 0;
|
|
foreach ($tabCtEntries as $strExt => $blnEntity) {
|
|
if (!isset($tabEligibleKeys[$strExt])) {
|
|
$intCtOrphans++;
|
|
}
|
|
}
|
|
|
|
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'],
|
|
'blocked_team' => $arrClass['blocked_team'],
|
|
'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,
|
|
'ct_active' => $intCtActive,
|
|
'ct_withdrawn' => intval($arrCtStats['withdrawn']),
|
|
'already_in_ct' => $intAlreadyInCt,
|
|
'will_update' => $intAlreadyInCt,
|
|
'to_create' => $intToCreate,
|
|
'will_create' => $intToCreate,
|
|
'ct_orphans' => $intCtOrphans,
|
|
'ct_without_external_id' => $intCtWithoutExt,
|
|
'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,
|
|
);
|
|
}
|