MSIN-4328 — Add custom field probe functionality to ChronoTrack API. Implement a new button for custom field synchronization in the admin interface, allowing for the testing of custom fields similar to import processes. Enhance backend logic to handle custom field creation and validation, improving the overall synchronization capabilities and user experience.
This commit is contained in:
@ -372,6 +372,9 @@ function fxChronotrackApiAdminRenderDiagnosticPanel($intCtEventId) {
|
||||
echo '<i class="fa fa-stethoscope" aria-hidden="true"></i> Lancer le diagnostic API</button> ';
|
||||
echo '<button type="button" class="btn btn-outline-secondary btn-sm mb-2" id="ct_api_btn_probe_push">';
|
||||
echo '<i class="fa fa-flask" aria-hidden="true"></i> Sonde (1 participant POST)</button> ';
|
||||
echo '<button type="button" class="btn btn-outline-secondary btn-sm mb-2" id="ct_api_btn_probe_custom" '
|
||||
. 'title="POST entry avec custom_Ms1Probe + relecture (teste si CT crée le champ comme à l’import)">';
|
||||
echo '<i class="fa fa-flask" aria-hidden="true"></i> Sonde custom field</button> ';
|
||||
echo '<button type="button" class="btn btn-outline-secondary btn-sm mb-2 d-none" id="ct_api_btn_diagnostic_copy">';
|
||||
echo 'Copier le rapport</button>';
|
||||
echo '<div id="ct_api_diagnostic_out" class="small text-muted">Cliquez sur « Lancer le diagnostic API » ou « Sonde ».</div>';
|
||||
@ -874,6 +877,57 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
});
|
||||
});
|
||||
|
||||
// MSIN-4328 — sonde custom_Ms1Probe (mimique import Custom)
|
||||
$('#ct_api_btn_probe_custom').on('click', function () {
|
||||
var $btn = $(this);
|
||||
var $out = $('#ct_api_diagnostic_out');
|
||||
$btn.prop('disabled', true);
|
||||
$out.html(waitHtml('Sonde custom field…'));
|
||||
$('#ct_api_btn_diagnostic_copy').addClass('d-none');
|
||||
$.post(strAjaxUrl, { a: 'sync_probe_custom', eve_id: intEveId }, function (res) {
|
||||
if (rejectIfSessionLost(res)) {
|
||||
return;
|
||||
}
|
||||
var strClass = (res && res.state === 'ok')
|
||||
? 'text-success'
|
||||
: ((res && res.state === 'partial') ? 'text-warning' : 'text-danger');
|
||||
var strHtml = '<div class="' + strClass + '"><strong>Sonde custom</strong> — '
|
||||
+ escHtml((res && res.message) ? res.message : 'Erreur') + '</div>';
|
||||
if (res) {
|
||||
strHtml += '<div class="small text-muted mt-1">field='
|
||||
+ escHtml(res.field_key || '') + ' sent='
|
||||
+ escHtml(String(res.field_value_sent || ''))
|
||||
+ ' present=' + (res.field_present ? 'oui' : 'non')
|
||||
+ ' read=' + escHtml(JSON.stringify(res.field_value_read))
|
||||
+ ' · ext=' + escHtml(String(res.external_id || ''))
|
||||
+ '</div>';
|
||||
}
|
||||
if (res && res.question_endpoints && res.question_endpoints.length) {
|
||||
strHtml += '<div class="small mt-2"><strong>Endpoints question (GET)</strong><ul class="mb-0 pl-3">';
|
||||
$.each(res.question_endpoints, function (_, ep) {
|
||||
strHtml += '<li><code>' + escHtml(ep.path) + '</code> → HTTP '
|
||||
+ escHtml(String(ep.http_code || '?'))
|
||||
+ ' (' + escHtml(ep.state || '') + ')</li>';
|
||||
});
|
||||
strHtml += '</ul></div>';
|
||||
}
|
||||
if (res && res.payload) {
|
||||
strHtml += '<pre class="small bg-light p-2 mt-2 mb-1" style="max-height:180px;overflow:auto;">'
|
||||
+ escHtml(JSON.stringify(res.payload, null, 2)) + '</pre>';
|
||||
}
|
||||
if (res && res.post_response) {
|
||||
strHtml += '<pre class="small bg-light p-2 mb-0" style="max-height:180px;overflow:auto;">'
|
||||
+ escHtml(res.post_response) + '</pre>';
|
||||
}
|
||||
$out.html(strHtml);
|
||||
$('#ct_api_btn_diagnostic_copy').removeClass('d-none');
|
||||
}, 'json').fail(function () {
|
||||
$out.html('<div class="text-danger">Erreur réseau (sonde custom).</div>');
|
||||
}).always(function () {
|
||||
$btn.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
var objPushPreview = null;
|
||||
|
||||
function renderBlockedPanel(res) {
|
||||
|
||||
@ -915,6 +915,170 @@ function fxChronotrackApiSyncProbePush($intEveId) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — sonde « custom field » : tente comme un import Custom
|
||||
* (POST entry avec custom_Ms1Probe*) + lit l’entry, + teste endpoints question.
|
||||
*/
|
||||
function fxChronotrackApiSyncProbeCustomField($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);
|
||||
|
||||
if (count($arrClass['transferable']) === 0) {
|
||||
return array('state' => 'error', 'message' => 'Aucun participant transférable pour la sonde custom.');
|
||||
}
|
||||
|
||||
// Préférer une entry déjà liée CT (maj légère, pas de création complète)
|
||||
$arrPick = null;
|
||||
foreach ($arrClass['transferable'] as $arrItem) {
|
||||
if (intval($arrItem['row']['ct_entry_id'] ?? 0) > 0) {
|
||||
$arrPick = $arrItem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($arrPick === null) {
|
||||
$arrPick = $arrClass['transferable'][0];
|
||||
}
|
||||
|
||||
$arrRow = $arrPick['row'];
|
||||
$strExternalId = (string)($arrPick['payload']['external_id'] ?? '');
|
||||
$intCtEntryId = intval($arrRow['ct_entry_id'] ?? 0);
|
||||
$strFieldKey = 'custom_Ms1Probe';
|
||||
$strValue = 'ms1-probe-' . date('YmdHis');
|
||||
|
||||
$tabQuestionEndpoints = array();
|
||||
foreach (array('question', 'custom_question', 'survey_question', 'reg_question') as $strEp) {
|
||||
$arrG = fxChronotrackApiOAuthApiGet('event/' . $intCtEventId . '/' . $strEp);
|
||||
$tabQuestionEndpoints[] = array(
|
||||
'path' => 'event/' . $intCtEventId . '/' . $strEp,
|
||||
'http_code' => intval($arrG['http']['http_code'] ?? 0),
|
||||
'state' => $arrG['state'] ?? 'error',
|
||||
'message' => isset($arrG['message']) ? substr((string)$arrG['message'], 0, 200) : '',
|
||||
);
|
||||
}
|
||||
|
||||
// Corps minimal : identifier + champ custom (mimique import « Custom »)
|
||||
$arrProbePayload = array(
|
||||
'external_id' => $strExternalId,
|
||||
$strFieldKey => $strValue,
|
||||
);
|
||||
if ($intCtEntryId > 0) {
|
||||
// Certaines maj CT acceptent l’id numérique
|
||||
$arrProbePayload['entry_id'] = (string)$intCtEntryId;
|
||||
}
|
||||
|
||||
$arrPost = fxChronotrackApiSyncPostEntries($intCtEventId, array($arrProbePayload));
|
||||
|
||||
$mixReadbackValue = null;
|
||||
$blnFieldPresent = false;
|
||||
$arrReadback = null;
|
||||
$strReadbackHow = '';
|
||||
|
||||
if ($intCtEntryId > 0) {
|
||||
$arrGet = fxChronotrackApiOAuthApiGet('entry/' . $intCtEntryId);
|
||||
$strReadbackHow = 'GET entry/' . $intCtEntryId;
|
||||
if (($arrGet['state'] ?? '') === 'ok' && is_array($arrGet['json'] ?? null)) {
|
||||
$arrReadback = $arrGet['json'];
|
||||
// CT peut wrapper { entry: {...} } ou renvoyer l’objet direct
|
||||
if (isset($arrReadback['entry']) && is_array($arrReadback['entry'])) {
|
||||
$arrReadback = $arrReadback['entry'];
|
||||
} elseif (isset($arrReadback['event_entry'][0]) && is_array($arrReadback['event_entry'][0])) {
|
||||
$arrReadback = $arrReadback['event_entry'][0];
|
||||
}
|
||||
if (array_key_exists($strFieldKey, $arrReadback)) {
|
||||
$blnFieldPresent = true;
|
||||
$mixReadbackValue = $arrReadback[$strFieldKey];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$blnFieldPresent) {
|
||||
// Repli : 1re page entries et recherche par external_id
|
||||
$arrList = fxChronotrackApiOAuthApiGet(
|
||||
'event/' . $intCtEventId . '/entry',
|
||||
array('page' => 1, 'page_size' => 50)
|
||||
);
|
||||
if ($strReadbackHow === '') {
|
||||
$strReadbackHow = 'GET event/.../entry page1';
|
||||
} else {
|
||||
$strReadbackHow .= ' + list page1';
|
||||
}
|
||||
$tabEnt = array();
|
||||
if (($arrList['state'] ?? '') === 'ok' && is_array($arrList['json'] ?? null)) {
|
||||
$tabEnt = fxChronotrackApiNormalizeEntityList($arrList['json'], 'entry');
|
||||
if (count($tabEnt) === 0 && isset($arrList['json']['event_entry']) && is_array($arrList['json']['event_entry'])) {
|
||||
$tabEnt = $arrList['json']['event_entry'];
|
||||
}
|
||||
}
|
||||
foreach ($tabEnt as $arrEnt) {
|
||||
if (!is_array($arrEnt)) {
|
||||
continue;
|
||||
}
|
||||
$strExt = trim((string)(
|
||||
$arrEnt['entry_external_id'] ?? $arrEnt['external_id'] ?? ''
|
||||
));
|
||||
if ($strExt !== $strExternalId) {
|
||||
continue;
|
||||
}
|
||||
$arrReadback = $arrEnt;
|
||||
if (array_key_exists($strFieldKey, $arrEnt)) {
|
||||
$blnFieldPresent = true;
|
||||
$mixReadbackValue = $arrEnt[$strFieldKey];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$strVerdict = 'POST ok mais champ absent au relecture (CT n’a pas créé le custom comme à l’import).';
|
||||
$strState = 'error';
|
||||
if (($arrPost['state'] ?? '') !== 'ok') {
|
||||
$strVerdict = 'POST refusé — ' . (string)($arrPost['message'] ?? 'erreur');
|
||||
$strState = 'error';
|
||||
} elseif ($blnFieldPresent && (string)$mixReadbackValue === $strValue) {
|
||||
$strVerdict = 'OK — CT a accepté et conservé ' . $strFieldKey . ' (création / slot custom plausible).';
|
||||
$strState = 'ok';
|
||||
} elseif ($blnFieldPresent) {
|
||||
$strVerdict = 'POST ok, champ présent mais valeur différente: '
|
||||
. json_encode($mixReadbackValue, JSON_UNESCAPED_UNICODE);
|
||||
$strState = 'partial';
|
||||
}
|
||||
|
||||
fxChronotrackApiSyncLog(
|
||||
$intEveId,
|
||||
intval($arrRow['par_id'] ?? 0),
|
||||
'probe_custom',
|
||||
($strState === 'ok') ? 'ok' : 'error',
|
||||
$strVerdict
|
||||
);
|
||||
|
||||
return array(
|
||||
'state' => $strState,
|
||||
'message' => $strVerdict,
|
||||
'ct_event_id' => $intCtEventId,
|
||||
'par_id' => intval($arrRow['par_id'] ?? 0),
|
||||
'external_id' => $strExternalId,
|
||||
'ct_entry_id' => $intCtEntryId,
|
||||
'field_key' => $strFieldKey,
|
||||
'field_value_sent' => $strValue,
|
||||
'field_present' => $blnFieldPresent,
|
||||
'field_value_read' => $mixReadbackValue,
|
||||
'payload' => $arrProbePayload,
|
||||
'post_http_code' => intval($arrPost['http']['http_code'] ?? 0),
|
||||
'post_response' => substr(trim((string)($arrPost['http']['body'] ?? '')), 0, 1500),
|
||||
'readback_how' => $strReadbackHow,
|
||||
'question_endpoints' => $tabQuestionEndpoints,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sonde perf — teste 10 / 50 / 100 / 200 entries (upsert, mêmes gens). MSIN-4328
|
||||
* Évite un push complet pour calibrer la taille de lot.
|
||||
|
||||
@ -170,6 +170,16 @@ switch ($strAction) {
|
||||
$tabRetour = fxChronotrackApiSyncProbePush($intEveId);
|
||||
break;
|
||||
|
||||
case 'sync_probe_custom':
|
||||
// MSIN-4328 — sonde création champ custom (comme import Custom)
|
||||
if ($intEveId <= 0) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
|
||||
break;
|
||||
}
|
||||
set_time_limit(120);
|
||||
$tabRetour = fxChronotrackApiSyncProbeCustomField($intEveId);
|
||||
break;
|
||||
|
||||
case 'sync_push_prepare':
|
||||
if ($intEveId <= 0) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
|
||||
|
||||
Reference in New Issue
Block a user