Files
ms1inscription-v5/php/chronotrack_api/fx_chronotrack_lifecycle.php

453 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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 dune 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 dune 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 dentry 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),
);
}