453 lines
17 KiB
PHP
453 lines
17 KiB
PHP
<?php
|
||
/**
|
||
* MSIN-4574 — Lifecycle CT : cancel / restore / WITHDRAWN / orphelins.
|
||
*/
|
||
|
||
function fxChronotrackApiSyncOrphanReasonLabel($strReason) {
|
||
switch ($strReason) {
|
||
case 'sans_external_id':
|
||
return 'Sans external ID';
|
||
case 'doublon':
|
||
return 'Doublon CT';
|
||
case 'hors_eligible':
|
||
default:
|
||
return 'Hors éligibles MS1';
|
||
}
|
||
}
|
||
|
||
function fxChronotrackApiSyncComputeCtOrphanRows(array $tabEntities, array $tabEligibleKeys) {
|
||
$tabSeenLinkedExt = array();
|
||
$tabOrphans = array();
|
||
|
||
foreach ($tabEntities as $arrEntity) {
|
||
if (!is_array($arrEntity)) {
|
||
continue;
|
||
}
|
||
$strEntryId = fxChronotrackApiEntityId($arrEntity);
|
||
if ($strEntryId === '') {
|
||
continue;
|
||
}
|
||
|
||
$strExt = fxChronotrackApiEntityExternalId($arrEntity);
|
||
$strReason = '';
|
||
|
||
if ($strExt === '') {
|
||
$strReason = 'sans_external_id';
|
||
} elseif (!isset($tabEligibleKeys[$strExt])) {
|
||
$strReason = 'hors_eligible';
|
||
} elseif (isset($tabSeenLinkedExt[$strExt])) {
|
||
$strReason = 'doublon';
|
||
} else {
|
||
$tabSeenLinkedExt[$strExt] = true;
|
||
continue;
|
||
}
|
||
|
||
$tabOrphans[] = array(
|
||
'entry_id' => $strEntryId,
|
||
'external_id' => $strExt,
|
||
'name' => fxChronotrackApiEntityEntryLabel($arrEntity),
|
||
'bib' => fxChronotrackApiRaceField($arrEntity, array('bib', 'bib_number', 'entry_bib')),
|
||
'entry_status' => fxChronotrackApiRaceField($arrEntity, array('entry_status', 'status')),
|
||
'race_id' => fxChronotrackApiRaceField($arrEntity, array('race_id', 'event_race_id')),
|
||
'reason' => $strReason,
|
||
'reason_label' => fxChronotrackApiSyncOrphanReasonLabel($strReason),
|
||
);
|
||
}
|
||
|
||
usort($tabOrphans, function ($a, $b) {
|
||
$strA = strtolower(($a['name'] ?? '') . ($a['external_id'] ?? '') . ($a['entry_id'] ?? ''));
|
||
$strB = strtolower(($b['name'] ?? '') . ($b['external_id'] ?? '') . ($b['entry_id'] ?? ''));
|
||
return strcmp($strA, $strB);
|
||
});
|
||
|
||
return $tabOrphans;
|
||
}
|
||
|
||
/**
|
||
* Entries CT présentes sur l'événement mais absentes du jeu éligible MS1 (lié, doublon, sans ext).
|
||
* MSIN-4574 — version légère : pas de build payloads/notes (sinon gèle à ~1300).
|
||
*/
|
||
function fxChronotrackApiSyncListCtOrphans($intEveId) {
|
||
$intEveId = intval($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);
|
||
// Pas de payloads / entry_notes — uniquement les clés external_id éligibles
|
||
$arrClass = fxChronotrackApiSyncClassifyParticipants(
|
||
$tabParticipants,
|
||
$arrRaceMap,
|
||
$intCtEventId,
|
||
false
|
||
);
|
||
|
||
$tabEligibleKeys = array();
|
||
foreach ($arrClass['transferable'] as $arrItem) {
|
||
if (!is_array($arrItem)) {
|
||
continue;
|
||
}
|
||
$strExt = trim((string)($arrItem['summary']['external_id'] ?? ''));
|
||
if ($strExt === '' || $strExt === '0') {
|
||
$strExt = (string)intval($arrItem['row']['par_id_original'] ?? 0);
|
||
}
|
||
if ($strExt !== '' && $strExt !== '0') {
|
||
$tabEligibleKeys[$strExt] = true;
|
||
}
|
||
}
|
||
|
||
$floatStart = microtime(true);
|
||
$arrFetch = fxChronotrackApiSyncFetchAllCtEntryEntities($intCtEventId);
|
||
$floatMs = (int)round((microtime(true) - $floatStart) * 1000);
|
||
if ($arrFetch['state'] !== 'ok') {
|
||
return array(
|
||
'state' => 'error',
|
||
'message' => $arrFetch['message'] ?? 'Erreur lecture entries CT',
|
||
'elapsed_ms' => $floatMs,
|
||
);
|
||
}
|
||
|
||
$tabOrphans = fxChronotrackApiSyncComputeCtOrphanRows($arrFetch['entities'], $tabEligibleKeys);
|
||
$intCtTotal = intval($arrFetch['count'] ?? count($arrFetch['entities']));
|
||
|
||
fxChronotrackApiSyncLog(
|
||
$intEveId,
|
||
0,
|
||
'reconcile',
|
||
'ok',
|
||
'list orphans=' . count($tabOrphans)
|
||
. ' ct_total=' . $intCtTotal
|
||
. ' eligible_keys=' . count($tabEligibleKeys)
|
||
. ' ms=' . $floatMs
|
||
);
|
||
|
||
return array(
|
||
'state' => 'ok',
|
||
'ct_event_id' => $intCtEventId,
|
||
'ct_total' => $intCtTotal,
|
||
'linked_count' => count($tabEligibleKeys),
|
||
'eligible_count' => count($arrClass['transferable']),
|
||
'count' => count($tabOrphans),
|
||
'orphans' => $tabOrphans,
|
||
'elapsed_ms' => $floatMs,
|
||
'message' => count($tabOrphans) . ' orphelin(s) sur ' . $intCtTotal . ' entries CT'
|
||
. ' (' . round($floatMs / 1000, 1) . ' s)',
|
||
);
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — participants d’une commande (y compris annulés / inactifs) pour WITHDRAW.
|
||
*/
|
||
function fxChronotrackApiSyncLoadParticipantsByPec($intEveId, $intPecId) {
|
||
global $objDatabase;
|
||
|
||
$intEveId = intval($intEveId);
|
||
$intPecId = intval($intPecId);
|
||
if ($intEveId <= 0 || $intPecId <= 0 || !isset($objDatabase) || !is_object($objDatabase)) {
|
||
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.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 p.eve_id = " . $intEveId
|
||
. " AND p.pec_id = " . $intPecId
|
||
. " ORDER BY p.rol_id, 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;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — libère les dossards MS1 d’une commande (disponibles à réassigner).
|
||
*/
|
||
function fxChronotrackApiSyncClearMs1BibsForPec($intPecId) {
|
||
global $objDatabase;
|
||
|
||
$intPecId = intval($intPecId);
|
||
if ($intPecId <= 0 || !isset($objDatabase) || !is_object($objDatabase)) {
|
||
return false;
|
||
}
|
||
$strNow = function_exists('fxGetDateTime') ? fxGetDateTime() : date('Y-m-d H:i:s');
|
||
$sql = "UPDATE resultats_participants SET"
|
||
. " no_bib = NULL,"
|
||
. " no_bib_remis = 0,"
|
||
. " no_bib_remis_date = NULL,"
|
||
. " no_bib_remis_par = NULL,"
|
||
. " par_date_bib = NULL,"
|
||
. " par_maj = '" . $objDatabase->fxEscape($strNow) . "'"
|
||
. " WHERE pec_id = " . $intPecId;
|
||
return (bool)$objDatabase->fxQuery($sql);
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — PUT/POST WITHDRAWN + bib vide sur une entry CT.
|
||
* Ne crée pas d’entry fantôme : exige entry_id (ct_entry_id) sauf si fourni explicitement.
|
||
*/
|
||
function fxChronotrackApiSyncWithdrawCtEntry($intEveId, array $arrRow, $intCtEventId = 0, array $arrRaceMap = null) {
|
||
$intEveId = intval($intEveId);
|
||
$strEntryId = preg_replace('/[^0-9]/', '', (string)($arrRow['ct_entry_id'] ?? $arrRow['entry_id'] ?? ''));
|
||
$strExternalId = trim((string)($arrRow['par_id_original'] ?? $arrRow['external_id'] ?? ''));
|
||
if ($strExternalId === '' || $strExternalId === '0') {
|
||
return array('state' => 'skip', 'message' => 'external_id manquant');
|
||
}
|
||
if ($strEntryId === '') {
|
||
return array('state' => 'skip', 'message' => 'ct_entry_id manquant — pas de WITHDRAW (éviter création)');
|
||
}
|
||
|
||
if ($intCtEventId <= 0) {
|
||
$arrConfig = fxChronotrackApiConfigGet($intEveId);
|
||
if ($arrConfig === null) {
|
||
return array('state' => 'skip', 'message' => 'Événement non lié à ChronoTrack');
|
||
}
|
||
$intCtEventId = intval($arrConfig['ct_event_id'] ?? 0);
|
||
}
|
||
if ($intCtEventId <= 0) {
|
||
return array('state' => 'skip', 'message' => 'ct_event_id manquant');
|
||
}
|
||
|
||
$arrPayload = array(
|
||
'external_id' => (string)intval($strExternalId),
|
||
'entry_status' => 'WITHDRAWN',
|
||
'status' => 'WITHDRAWN',
|
||
'bib' => '',
|
||
'entry_id' => $strEntryId,
|
||
);
|
||
|
||
$intCtRaceId = intval($arrRow['race_id'] ?? $arrRow['ct_race_id'] ?? 0);
|
||
if ($intCtRaceId <= 0) {
|
||
if ($arrRaceMap === null) {
|
||
$arrRaceMap = fxChronotrackApiRaceMapGetForEvent($intEveId);
|
||
}
|
||
$intEprId = intval($arrRow['epr_id'] ?? 0);
|
||
$intCtRaceId = intval($arrRaceMap[$intEprId]['ct_race_id'] ?? 0);
|
||
}
|
||
if ($intCtRaceId > 0) {
|
||
$arrPayload['race_id'] = $intCtRaceId;
|
||
}
|
||
$arrPayload['event_id'] = $intCtEventId;
|
||
|
||
$arrWrite = fxChronotrackApiSyncPutEntry($strEntryId, $arrPayload);
|
||
$intParId = intval($arrRow['par_id'] ?? 0);
|
||
if (($arrWrite['state'] ?? '') === 'ok') {
|
||
fxChronotrackApiSyncLog(
|
||
$intEveId,
|
||
$intParId,
|
||
'withdraw',
|
||
'ok',
|
||
'WITHDRAWN entry_id=' . $strEntryId . ' external_id=' . $arrPayload['external_id']
|
||
);
|
||
return array(
|
||
'state' => 'ok',
|
||
'entry_id' => $strEntryId,
|
||
'external_id' => $arrPayload['external_id'],
|
||
'message' => 'WITHDRAWN',
|
||
);
|
||
}
|
||
|
||
$strErr = (string)($arrWrite['message'] ?? 'erreur WITHDRAW');
|
||
fxChronotrackApiSyncLog($intEveId, $intParId, 'withdraw', 'error', $strErr);
|
||
return array('state' => 'error', 'message' => $strErr, 'entry_id' => $strEntryId);
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — après annulation MS1 : free bib + WITHDRAWN CT (capitaine si équipe).
|
||
*/
|
||
function fxChronotrackApiSyncOnMs1Cancel($intEveId, $intPecId) {
|
||
$intEveId = intval($intEveId);
|
||
$intPecId = intval($intPecId);
|
||
$arrOut = array(
|
||
'state' => 'ok',
|
||
'bibs_cleared' => false,
|
||
'ct_attempted' => false,
|
||
'withdrawn' => 0,
|
||
'skipped' => 0,
|
||
'errors' => array(),
|
||
'message' => '',
|
||
);
|
||
|
||
$arrOut['bibs_cleared'] = fxChronotrackApiSyncClearMs1BibsForPec($intPecId);
|
||
|
||
if ($intEveId <= 0 || !function_exists('fxChronotrackApiConfigGet')) {
|
||
$arrOut['message'] = 'bibs MS1 seulement';
|
||
return $arrOut;
|
||
}
|
||
$arrConfig = fxChronotrackApiConfigGet($intEveId);
|
||
if ($arrConfig === null || !fxChronotrackApiSettingsConfigured()) {
|
||
$arrOut['message'] = 'bibs MS1 — pas de lien ChronoTrack';
|
||
return $arrOut;
|
||
}
|
||
|
||
$arrOut['ct_attempted'] = true;
|
||
$intCtEventId = intval($arrConfig['ct_event_id'] ?? 0);
|
||
$arrRaceMap = fxChronotrackApiRaceMapGetForEvent($intEveId);
|
||
$tabRows = fxChronotrackApiSyncLoadParticipantsByPec($intEveId, $intPecId);
|
||
if (count($tabRows) === 0) {
|
||
$arrOut['message'] = 'aucun participant pour pec';
|
||
return $arrOut;
|
||
}
|
||
|
||
$blnTeam = false;
|
||
foreach ($tabRows as $arrRow) {
|
||
if (fxChronotrackApiSyncIsTeamRow($arrRow)) {
|
||
$blnTeam = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
$tabTargets = array();
|
||
if ($blnTeam) {
|
||
$arrRep = fxChronotrackApiSyncPickTeamRepresentative($tabRows);
|
||
if (is_array($arrRep)) {
|
||
$tabTargets[] = $arrRep;
|
||
}
|
||
} else {
|
||
$tabTargets = $tabRows;
|
||
}
|
||
|
||
foreach ($tabTargets as $arrTarget) {
|
||
$arrW = fxChronotrackApiSyncWithdrawCtEntry($intEveId, $arrTarget, $intCtEventId, $arrRaceMap);
|
||
$strState = (string)($arrW['state'] ?? '');
|
||
if ($strState === 'ok') {
|
||
$arrOut['withdrawn']++;
|
||
} elseif ($strState === 'skip') {
|
||
$arrOut['skipped']++;
|
||
} else {
|
||
$arrOut['errors'][] = (string)($arrW['message'] ?? 'erreur');
|
||
}
|
||
}
|
||
|
||
if (count($arrOut['errors']) > 0) {
|
||
$arrOut['state'] = 'error';
|
||
$arrOut['message'] = 'WITHDRAW partiel — ' . implode(' ; ', $arrOut['errors']);
|
||
} else {
|
||
$arrOut['message'] = 'withdrawn=' . $arrOut['withdrawn']
|
||
. ' skipped=' . $arrOut['skipped']
|
||
. ' bibs=' . ($arrOut['bibs_cleared'] ? 'ok' : 'fail');
|
||
}
|
||
return $arrOut;
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — rétablissement : bumper par_maj pour re-push CONF (dossard à réassigner).
|
||
*/
|
||
function fxChronotrackApiSyncOnMs1Restore($intEveId, $intPecId) {
|
||
global $objDatabase;
|
||
|
||
$intEveId = intval($intEveId);
|
||
$intPecId = intval($intPecId);
|
||
if ($intPecId <= 0 || !isset($objDatabase) || !is_object($objDatabase)) {
|
||
return array('state' => 'skip', 'message' => 'pec manquant');
|
||
}
|
||
$strNow = function_exists('fxGetDateTime') ? fxGetDateTime() : date('Y-m-d H:i:s');
|
||
$sql = "UPDATE resultats_participants SET par_maj = '" . $objDatabase->fxEscape($strNow) . "'"
|
||
. " WHERE pec_id = " . $intPecId;
|
||
$blnOk = (bool)$objDatabase->fxQuery($sql);
|
||
if ($blnOk && $intEveId > 0) {
|
||
fxChronotrackApiSyncLog($intEveId, 0, 'restore_bump', 'ok', 'pec_id=' . $intPecId . ' par_maj bumpé');
|
||
}
|
||
return array('state' => $blnOk ? 'ok' : 'error', 'message' => $blnOk ? 'par_maj bumpé' : 'SQL fail');
|
||
}
|
||
|
||
/**
|
||
* MSIN-4574 — retire (WITHDRAWN + clear bib) les orphelins CT hors éligibles MS1.
|
||
*/
|
||
function fxChronotrackApiSyncWithdrawCtOrphans($intEveId) {
|
||
$intEveId = intval($intEveId);
|
||
$arrList = fxChronotrackApiSyncListCtOrphans($intEveId);
|
||
if (($arrList['state'] ?? '') !== 'ok') {
|
||
return $arrList;
|
||
}
|
||
|
||
$intCtEventId = intval($arrList['ct_event_id'] ?? 0);
|
||
$arrRaceMap = fxChronotrackApiRaceMapGetForEvent($intEveId);
|
||
$tabOrphans = is_array($arrList['orphans'] ?? null) ? $arrList['orphans'] : array();
|
||
$intOk = 0;
|
||
$intSkip = 0;
|
||
$tabErrors = array();
|
||
$tabDone = array();
|
||
|
||
foreach ($tabOrphans as $arrOrphan) {
|
||
// hors_eligible = annulés / transferts / ex-membres ; doublons aussi à retirer
|
||
$strReason = (string)($arrOrphan['reason'] ?? '');
|
||
if ($strReason !== 'hors_eligible' && $strReason !== 'doublon') {
|
||
$intSkip++;
|
||
continue;
|
||
}
|
||
$arrRow = array(
|
||
'entry_id' => $arrOrphan['entry_id'] ?? '',
|
||
'ct_entry_id' => $arrOrphan['entry_id'] ?? '',
|
||
'external_id' => $arrOrphan['external_id'] ?? '',
|
||
'par_id_original' => $arrOrphan['external_id'] ?? '',
|
||
'race_id' => intval($arrOrphan['race_id'] ?? 0),
|
||
'epr_id' => 0,
|
||
'par_id' => 0,
|
||
);
|
||
$arrW = fxChronotrackApiSyncWithdrawCtEntry($intEveId, $arrRow, $intCtEventId, $arrRaceMap);
|
||
$strState = (string)($arrW['state'] ?? '');
|
||
if ($strState === 'ok') {
|
||
$intOk++;
|
||
$tabDone[] = array(
|
||
'entry_id' => $arrOrphan['entry_id'] ?? '',
|
||
'external_id' => $arrOrphan['external_id'] ?? '',
|
||
'name' => $arrOrphan['name'] ?? '',
|
||
);
|
||
} elseif ($strState === 'skip') {
|
||
$intSkip++;
|
||
} else {
|
||
$tabErrors[] = ($arrOrphan['name'] ?? '') . ': ' . ($arrW['message'] ?? 'erreur');
|
||
}
|
||
}
|
||
|
||
fxChronotrackApiSyncLog(
|
||
$intEveId,
|
||
0,
|
||
'reconcile',
|
||
count($tabErrors) > 0 ? 'error' : 'ok',
|
||
'orphans WITHDRAWN ok=' . $intOk . ' skip=' . $intSkip . ' err=' . count($tabErrors)
|
||
);
|
||
|
||
return array(
|
||
'state' => count($tabErrors) > 0 ? 'error' : 'ok',
|
||
'message' => 'WITHDRAWN ' . $intOk . ' orphelin(s)'
|
||
. ($intSkip > 0 ? ', ' . $intSkip . ' ignoré(s)' : '')
|
||
. (count($tabErrors) > 0 ? ' — erreurs: ' . implode(' ; ', $tabErrors) : ''),
|
||
'withdrawn_count' => $intOk,
|
||
'skipped_count' => $intSkip,
|
||
'errors' => $tabErrors,
|
||
'withdrawn' => $tabDone,
|
||
'orphan_count' => count($tabOrphans),
|
||
);
|
||
}
|
||
|