566 lines
21 KiB
PHP
566 lines
21 KiB
PHP
<?php
|
||
/**
|
||
* MSIN API ChronoTrack — classify, preview, logs.
|
||
* MSIN-4574 : payload / push / read / lifecycle / cron / probe = fichiers dédiés.
|
||
*/
|
||
|
||
// MSIN-4574 — vitesse 2026 : 1 POST CT = jusqu’à 100 entries AVEC dossard.
|
||
// 2e passe bib uniquement pour conflits (pas 1300 PUT systématiques).
|
||
define('MSIN_API_CHRONOTRACK_SYNC_BATCH_SIZE', 100);
|
||
define('MSIN_API_CHRONOTRACK_SYNC_CHUNK_SIZE', 100);
|
||
define('MSIN_API_CHRONOTRACK_SYNC_BIB_CHUNK_SIZE', 40);
|
||
|
||
// MSIN-4574 — modules extraits (si sync chargé hors bootstrap).
|
||
if (!function_exists('fxChronotrackApiSyncBuildEntryPayload')) {
|
||
require_once __DIR__ . '/fx_chronotrack_payload.php';
|
||
}
|
||
if (!function_exists('fxChronotrackApiSyncPutEntry')) {
|
||
require_once __DIR__ . '/fx_chronotrack_push.php';
|
||
}
|
||
if (!function_exists('fxChronotrackApiSyncFetchAllCtEntryEntities')) {
|
||
require_once __DIR__ . '/fx_chronotrack_read.php';
|
||
}
|
||
if (!function_exists('fxChronotrackApiSyncWithdrawCtEntry')) {
|
||
require_once __DIR__ . '/fx_chronotrack_lifecycle.php';
|
||
}
|
||
if (!function_exists('fxChronotrackApiSyncRunAutoCron')) {
|
||
require_once __DIR__ . '/fx_chronotrack_cron.php';
|
||
}
|
||
if (!function_exists('fxChronotrackApiSyncProbePush')) {
|
||
require_once __DIR__ . '/fx_chronotrack_probe.php';
|
||
}
|
||
|
||
/**
|
||
* MSIN-4328 — URL admin ChronoTrack (fiche athlète).
|
||
*/
|
||
function fxChronotrackApiAdminEntryUrl($intCtEventId, $strCtEntryId) {
|
||
$intCtEventId = intval($intCtEventId);
|
||
$strCtEntryId = preg_replace('/[^0-9]/', '', (string)$strCtEntryId);
|
||
if ($intCtEventId <= 0 || $strCtEntryId === '') {
|
||
return '';
|
||
}
|
||
return 'https://admin.chronotrack.com/admin/entry/index/eventID/'
|
||
. $intCtEventId . '?entryID=' . $strCtEntryId;
|
||
}
|
||
|
||
|
||
function fxChronotrackApiSyncFormatTeamBlockMessage(array $arrRow) {
|
||
$arrParts = array('Nom d\'équipe manquant — corriger dans MS1');
|
||
$strNoEquipe = trim((string)($arrRow['no_equipe'] ?? ''));
|
||
if ($strNoEquipe !== '' && $strNoEquipe !== '0') {
|
||
$arrParts[] = 'no équipe #' . $strNoEquipe;
|
||
}
|
||
return implode(' · ', $arrParts);
|
||
}
|
||
|
||
|
||
function fxChronotrackApiSyncGroupBlockedByType(array $tabBlocked) {
|
||
$tabTypeLabels = array(
|
||
'team' => 'Équipes (nom manquant)',
|
||
'team_covered' => 'Membres équipe (couverts par EQ.)',
|
||
'duplicate_bib' => 'Dossard en double (individuel)',
|
||
'no_bib' => 'Dossard manquant',
|
||
'no_sex' => 'Sexe non reconnu',
|
||
'no_first_name' => 'Prénom manquant',
|
||
'no_last_name' => 'Nom manquant',
|
||
'no_race' => 'Épreuve non mappée',
|
||
'no_external' => 'External ID manquant',
|
||
);
|
||
$tabTypeOrder = array(
|
||
'team', 'team_covered', 'duplicate_bib', 'no_bib', 'no_sex',
|
||
'no_first_name', 'no_last_name', '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, $blnBuildPayloads = true) {
|
||
$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);
|
||
$strEprLabel = fxChronotrackApiSyncEpreuveLabel($arrRow);
|
||
// MSIN-4328 — nom d’épreuve en premier (epr_id en secours)
|
||
if ($strEprLabel !== '') {
|
||
$strMsg = '« ' . $strEprLabel . ' » (epr_id ' . $intEprId . ') non mappée vers ChronoTrack';
|
||
} else {
|
||
$strMsg = 'Épreuve MS1 (epr_id ' . $intEprId . ') non mappée vers ChronoTrack';
|
||
}
|
||
$tabBlocked[] = array(
|
||
'code' => 'no_race',
|
||
'message' => $strMsg,
|
||
'epr_id' => $intEprId,
|
||
'epr_nom' => $strEprLabel,
|
||
) + $arrSummary;
|
||
continue;
|
||
}
|
||
|
||
$tabCandidates[] = $arrRow;
|
||
}
|
||
|
||
// MSIN-4444 — séparer solo / équipes ; 1 entry CT = capitaine (évite doublon dossard partagé)
|
||
$tabSoloCandidates = array();
|
||
$tabTeamGroups = array();
|
||
foreach ($tabCandidates as $arrRow) {
|
||
if (fxChronotrackApiSyncIsTeamRow($arrRow)) {
|
||
$strTeamKey = fxChronotrackApiSyncTeamGroupKey($arrRow);
|
||
if (!isset($tabTeamGroups[$strTeamKey])) {
|
||
$tabTeamGroups[$strTeamKey] = array();
|
||
}
|
||
$tabTeamGroups[$strTeamKey][] = $arrRow;
|
||
} else {
|
||
$tabSoloCandidates[] = $arrRow;
|
||
}
|
||
}
|
||
|
||
$tabBibCounts = array();
|
||
foreach ($tabSoloCandidates as $arrRow) {
|
||
$strBib = fxChronotrackApiSyncExtractBib($arrRow);
|
||
if ($strBib !== '') {
|
||
if (!isset($tabBibCounts[$strBib])) {
|
||
$tabBibCounts[$strBib] = 0;
|
||
}
|
||
$tabBibCounts[$strBib]++;
|
||
}
|
||
}
|
||
|
||
$tabTeamReps = array();
|
||
foreach ($tabTeamGroups as $strTeamKey => $tabMembers) {
|
||
$arrRep = fxChronotrackApiSyncPickTeamRepresentative($tabMembers);
|
||
if ($arrRep === null) {
|
||
continue;
|
||
}
|
||
$tabTeamReps[$strTeamKey] = array(
|
||
'rep' => $arrRep,
|
||
'members' => $tabMembers,
|
||
);
|
||
$strBib = fxChronotrackApiSyncExtractBib($arrRep);
|
||
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;
|
||
$intBlockedTeamCovered = 0;
|
||
$intBlockedNoBib = 0;
|
||
$intBlockedNoSex = 0;
|
||
$intBlockedNoFirstName = 0;
|
||
$intBlockedNoLastName = 0;
|
||
$intBlockedDuplicateBib = 0;
|
||
|
||
foreach ($tabSoloCandidates 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;
|
||
}
|
||
|
||
// MSIN-4328 — CT refuse sans first_name / last_name
|
||
$strPrenom = trim((string)($arrRow['par_prenom'] ?? ''));
|
||
$strNom = trim((string)($arrRow['par_nom'] ?? ''));
|
||
if ($strPrenom === '') {
|
||
$intBlockedNoFirstName++;
|
||
$tabBlocked[] = array(
|
||
'code' => 'no_first_name',
|
||
'message' => 'Prénom manquant — corriger dans MS1',
|
||
) + $arrSummary;
|
||
continue;
|
||
}
|
||
if ($strNom === '') {
|
||
$intBlockedNoLastName++;
|
||
$tabBlocked[] = array(
|
||
'code' => 'no_last_name',
|
||
'message' => 'Nom manquant — corriger dans MS1',
|
||
) + $arrSummary;
|
||
continue;
|
||
}
|
||
|
||
$arrPayload = null;
|
||
if ($blnBuildPayloads) {
|
||
$arrPayload = fxChronotrackApiSyncBuildEntryPayload($arrRow, $arrRaceMap, $intCtEventId);
|
||
if ($arrPayload === null) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
$tabTransferable[] = array(
|
||
'row' => $arrRow,
|
||
'payload' => $arrPayload,
|
||
'summary' => $arrSummary,
|
||
);
|
||
}
|
||
|
||
// MSIN-4444 — équipes : prénom EQ. / nom = nom d'équipe (capitaine)
|
||
foreach ($tabTeamReps as $arrGroup) {
|
||
$arrRep = $arrGroup['rep'];
|
||
$tabMembers = $arrGroup['members'];
|
||
$intRepParId = intval($arrRep['par_id'] ?? 0);
|
||
|
||
foreach ($tabMembers as $arrMember) {
|
||
if (intval($arrMember['par_id'] ?? 0) === $intRepParId) {
|
||
continue;
|
||
}
|
||
$intBlockedTeamCovered++;
|
||
$tabBlocked[] = array(
|
||
'code' => 'team_covered',
|
||
'message' => 'Membre d\'équipe — couvert par EQ. / nom d\'équipe (capitaine)',
|
||
) + fxChronotrackApiSyncParticipantSummary($arrMember);
|
||
}
|
||
|
||
$arrSummary = fxChronotrackApiSyncParticipantSummary($arrRep);
|
||
$strTeamName = fxChronotrackApiSyncTeamName($arrRep);
|
||
if ($strTeamName === '') {
|
||
$intBlockedTeam++;
|
||
$tabBlocked[] = array(
|
||
'code' => 'team',
|
||
'message' => fxChronotrackApiSyncFormatTeamBlockMessage($arrRep),
|
||
) + $arrSummary;
|
||
continue;
|
||
}
|
||
$arrSummary['name'] = 'EQ. ' . $strTeamName;
|
||
$arrSummary['team_name'] = $strTeamName;
|
||
|
||
$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 (équipe) — corriger dans MS1',
|
||
) + $arrSummary;
|
||
continue;
|
||
}
|
||
if (!fxChronotrackApiSyncHasValidSex($arrRep['par_sexe'] ?? '')) {
|
||
$intBlockedNoSex++;
|
||
$strSexeMs1 = trim((string)($arrRep['par_sexe'] ?? ''));
|
||
$tabBlocked[] = array(
|
||
'code' => 'no_sex',
|
||
'message' => 'Sexe capitaine « ' . $strSexeMs1 . ' » — mapping CT à définir',
|
||
) + $arrSummary;
|
||
continue;
|
||
}
|
||
|
||
$arrPayload = null;
|
||
if ($blnBuildPayloads) {
|
||
$arrPayload = fxChronotrackApiSyncBuildEntryPayload($arrRep, $arrRaceMap, $intCtEventId);
|
||
if ($arrPayload === null) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
$tabTransferable[] = array(
|
||
'row' => $arrRep,
|
||
'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,
|
||
'country_coverage' => fxChronotrackApiSyncCountryCoverageFromTransferable($tabTransferable),
|
||
'blocked_no_bib' => $intBlockedNoBib,
|
||
'blocked_no_sex' => $intBlockedNoSex,
|
||
'blocked_no_first_name' => $intBlockedNoFirstName,
|
||
'blocked_no_last_name' => $intBlockedNoLastName,
|
||
'blocked_duplicate_bib' => $intBlockedDuplicateBib,
|
||
'blocked_team' => $intBlockedTeam + $intBlockedTeamCovered,
|
||
'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 fxChronotrackApiSyncLog($intEveId, $intParId, $strAction, $strStatus, $strMessage) {
|
||
global $objDatabase;
|
||
|
||
if (!isset($objDatabase) || !is_object($objDatabase)) {
|
||
error_log('MSIN-4328 sync_log: $objDatabase absent — action=' . $strAction);
|
||
return false;
|
||
}
|
||
|
||
$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() . "'";
|
||
$blnOk = (bool)$objDatabase->fxQuery($sql);
|
||
if (!$blnOk) {
|
||
// Sans ça, push/cron « OK » mais Voir les logs = vide (INSERT silencieux).
|
||
error_log('MSIN-4328 sync_log INSERT failed eve=' . intval($intEveId)
|
||
. ' action=' . $strAction . ' sql_err=' . (isset($objDatabase->con) ? mysqli_error($objDatabase->con) : '?'));
|
||
}
|
||
return $blnOk;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4328 — derniers logs sync pour un événement (écran admin).
|
||
*/
|
||
function fxChronotrackApiSyncLogList($intEveId, $intLimit = 50) {
|
||
global $objDatabase;
|
||
|
||
$intEveId = intval($intEveId);
|
||
$intLimit = max(1, min(200, intval($intLimit)));
|
||
if ($intEveId <= 0) {
|
||
return array('state' => 'error', 'message' => 'eve_id manquant', 'logs' => array());
|
||
}
|
||
|
||
$sql = "SELECT log_id, eve_id, par_id, action, status, message, created_at"
|
||
. " FROM api_chronotrack_sync_log"
|
||
. " WHERE eve_id = " . $intEveId
|
||
. " ORDER BY log_id DESC"
|
||
. " LIMIT " . $intLimit;
|
||
$arrRows = $objDatabase->fxGetResults($sql);
|
||
$tabOut = array();
|
||
if (is_array($arrRows)) {
|
||
for ($i = 1; $i <= count($arrRows); $i++) {
|
||
$tabOut[] = $arrRows[$i];
|
||
}
|
||
}
|
||
return array(
|
||
'state' => 'ok',
|
||
'logs' => $tabOut,
|
||
'count' => count($tabOut),
|
||
);
|
||
}
|
||
|
||
|
||
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,"
|
||
. " (SELECT pay_nom_en FROM inscriptions_pays WHERE pay_id = p.pay_id) AS country_name";
|
||
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,"
|
||
. " (SELECT pay_nom_en FROM inscriptions_pays WHERE pay_id = p.pay_id) AS country_name";
|
||
}
|
||
|
||
$sql = "SELECT p.par_id, p.par_id_original, p.eve_id, 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.pay_id, p.pro_id,"
|
||
. " p.par_naissance, p.no_bib, p.no_bib_remis, p.no_bib_remis_date, p.par_date_bib, p.par_statut_course,"
|
||
. " p.par_ville, p.par_adresse, p.par_codepostal,"
|
||
. " p.is_cancelled, p.par_maj, p.ct_entry_id,"
|
||
. " ec.pec_equipe, ec.no_equipe, ec.pec_nom_equipe,"
|
||
. " ie.epr_nom_fr AS epr_nom, ie.epr_type_fr AS epr_type,"
|
||
. " (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"
|
||
. " LEFT JOIN inscriptions_epreuves ie ON ie.epr_id = p.epr_id"
|
||
. " 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 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);
|
||
// MSIN-4328 — preview rapide : pas de build payloads / entry_notes (seulement compteurs + exclus)
|
||
$arrClass = fxChronotrackApiSyncClassifyParticipants($tabParticipants, $arrRaceMap, $intCtEventId, false);
|
||
|
||
$intMs1Total = count($tabParticipants);
|
||
$intTransferable = count($arrClass['transferable']);
|
||
|
||
// MSIN-4328 — preview MS1 seul (rapide). Plus de scan CT fiche-par-fiche :
|
||
// le push upsert crée/maj sans avoir besoin du inventaire Live ici.
|
||
$strLastPushAt = trim((string)($arrConfig['last_push_at'] ?? ''));
|
||
$tabPending = fxChronotrackApiSyncFilterChangedSincePush($arrClass['transferable'], $strLastPushAt);
|
||
$intPendingPush = count($tabPending);
|
||
|
||
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'],
|
||
// Pas de liste plate « blocked » (doublon lourd) — l’UI utilise blocked_by_type
|
||
'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'],
|
||
'ct_total' => null,
|
||
'ct_total_loaded' => false,
|
||
'already_in_ct' => null,
|
||
'will_update' => null,
|
||
'to_create' => null,
|
||
'will_create' => null,
|
||
'ct_orphans' => null,
|
||
'ct_without_external_id' => null,
|
||
'to_push' => $intPendingPush,
|
||
'pending_push' => $intPendingPush,
|
||
'country_coverage' => $arrClass['country_coverage'] ?? array(),
|
||
'last_push_at' => ($strLastPushAt !== '' && $strLastPushAt !== '0000-00-00 00:00:00')
|
||
? $strLastPushAt
|
||
: null,
|
||
'last_push_ok_count' => intval($arrConfig['last_push_ok_count'] ?? 0),
|
||
'is_first_push' => ($strLastPushAt === '' || $strLastPushAt === '0000-00-00 00:00:00'),
|
||
);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|