Refactor ChronoTrack API synchronization to streamline entry counting and improve performance. Introduce new functions for fetching total entries without detailed scanning, enhancing efficiency. Adjust AJAX handling for synchronization processes and update timeout settings for better reliability. This change aims to optimize data handling during participant synchronization.
This commit is contained in:
@ -101,18 +101,37 @@ function fxChronotrackApiAdminRenderEventPage($intEveId, $strLangue = 'fr', $bln
|
||||
fxChronotrackApiAdminRenderEventLinkPicker($blnClosed);
|
||||
}
|
||||
|
||||
if ($arrConfig !== null) {
|
||||
fxChronotrackApiAdminRenderStatusActions($intEveId, $strStatus, $blnEventLinked);
|
||||
}
|
||||
|
||||
if ($arrConfig !== null) {
|
||||
fxChronotrackApiAdminRenderRaceMapping($intEveId, $arrConfig, $strLangue, $blnClosed, $blnEventLinked);
|
||||
}
|
||||
|
||||
// Sync d’abord (usage courant) ; config repliable ensuite. MSIN-4328
|
||||
if ($arrConfig !== null && $blnEventLinked && $blnConfigured) {
|
||||
fxChronotrackApiAdminRenderPushPanel($intEveId, $blnClosed);
|
||||
}
|
||||
|
||||
if ($arrConfig !== null && $blnEventLinked) {
|
||||
$blnHasSavedMapping = false;
|
||||
$arrMapRows = fxChronotrackApiRaceMapGetForEvent($intEveId);
|
||||
foreach ($arrMapRows as $arrMapRow) {
|
||||
if (intval($arrMapRow['ct_race_id'] ?? 0) > 0) {
|
||||
$blnHasSavedMapping = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$strConfigExpanded = $blnHasSavedMapping ? '' : ' show';
|
||||
echo '<div class="card mb-4" id="ct_api_config_card">';
|
||||
echo '<div class="card-header py-2">';
|
||||
echo '<a class="d-block text-dark' . ($blnHasSavedMapping ? ' collapsed' : '') . '" data-toggle="collapse" '
|
||||
. 'href="#ct_api_config_collapse" role="button" aria-expanded="'
|
||||
. ($blnHasSavedMapping ? 'false' : 'true') . '">';
|
||||
echo 'Configuration <span class="text-muted small">(cycle de vie, mapping épreuves)</span>';
|
||||
echo '</a></div>';
|
||||
echo '<div class="collapse' . $strConfigExpanded . '" id="ct_api_config_collapse"><div class="card-body pt-3">';
|
||||
fxChronotrackApiAdminRenderStatusActions($intEveId, $strStatus, $blnEventLinked);
|
||||
fxChronotrackApiAdminRenderRaceMapping($intEveId, $arrConfig, $strLangue, $blnClosed, $blnEventLinked);
|
||||
echo '</div></div></div>';
|
||||
} elseif ($arrConfig !== null) {
|
||||
fxChronotrackApiAdminRenderStatusActions($intEveId, $strStatus, $blnEventLinked);
|
||||
fxChronotrackApiAdminRenderRaceMapping($intEveId, $arrConfig, $strLangue, $blnClosed, $blnEventLinked);
|
||||
}
|
||||
|
||||
if ($blnEventLinked && $blnConfigured) {
|
||||
fxChronotrackApiAdminRenderDiagnosticPanel(intval($arrConfig['ct_event_id']));
|
||||
}
|
||||
@ -127,9 +146,9 @@ function fxChronotrackApiAdminRenderEventPage($intEveId, $strLangue = 'fr', $bln
|
||||
}
|
||||
|
||||
function fxChronotrackApiAdminRenderStatusActions($intEveId, $strStatus, $blnEventLinked = true) {
|
||||
$strStep = $blnEventLinked ? '1' : '2';
|
||||
echo '<div class="card mb-4"><div class="card-header">' . $strStep . '. Cycle de vie sync</div><div class="card-body">';
|
||||
echo '<p class="small text-muted">Activer avant la course ; <strong>Terminer</strong> après la course pour bloquer la sync auto et les changements de config.</p>';
|
||||
echo '<div class="card mb-3 border"><div class="card-header py-2">Cycle de vie sync</div><div class="card-body py-3">';
|
||||
echo '<p class="small text-muted mb-2">Activer avant la course ; <strong>Terminer</strong> après pour bloquer la sync auto '
|
||||
. 'et les changements de config. (La sync auto différentielle viendra ensuite.)</p>';
|
||||
echo '<div class="btn-group" role="group">';
|
||||
if ($strStatus === MSIN_API_CHRONOTRACK_SYNC_LIE) {
|
||||
echo '<button type="button" class="btn btn-warning btn-sm ct-api-set-status" data-status="' . MSIN_API_CHRONOTRACK_SYNC_ACTIF . '">Activer la sync</button>';
|
||||
@ -160,8 +179,7 @@ function fxChronotrackApiAdminRenderRaceMapping($intEveId, $arrConfig, $strLangu
|
||||
}
|
||||
}
|
||||
|
||||
$strStep = $blnEventLinked ? '2' : '3';
|
||||
echo '<div class="card mb-4"><div class="card-header">' . $strStep . '. Mapping épreuves MS1 → races ChronoTrack</div><div class="card-body">';
|
||||
echo '<div class="card mb-0 border"><div class="card-header py-2">Mapping épreuves MS1 → races ChronoTrack</div><div class="card-body py-3">';
|
||||
if ($arrEpreuves === null || count($arrEpreuves) === 0) {
|
||||
echo '<p class="text-muted mb-0">Aucune épreuve MS1 pour cet événement.</p></div></div>';
|
||||
return;
|
||||
@ -227,26 +245,23 @@ function fxChronotrackApiAdminRenderRaceMapping($intEveId, $arrConfig, $strLangu
|
||||
}
|
||||
|
||||
function fxChronotrackApiAdminRenderPushPanel($intEveId, $blnClosed = false) {
|
||||
global $vDomaine;
|
||||
echo '<div class="card mb-4 border-primary" id="ct_api_push_card">';
|
||||
echo '<div class="card-header">3. Synchronisation manuelle (participants)</div>';
|
||||
echo '<div class="card-header">Synchronisation manuelle (participants)</div>';
|
||||
echo '<div class="card-body">';
|
||||
if ($blnClosed) {
|
||||
echo '<div class="alert alert-warning py-2 small mb-2">Événement fermé — push manuel autorisé pour tests ou rattrapage.</div>';
|
||||
}
|
||||
echo '<p class="small text-muted mb-2">Push manuel · External ID = <code>par_id_original</code> · Pas de sync auto.</p>';
|
||||
echo '<p class="small text-muted mb-2">Push <strong>différentiel</strong> MS1 → ChronoTrack '
|
||||
. '(nouveaux / modifiés depuis le dernier push OK). External ID = <code>par_id_original</code>.</p>';
|
||||
echo '<div id="ct_api_push_summary" class="mb-2 text-muted small">Cliquez <strong>Actualiser</strong>.</div>';
|
||||
echo '<div id="ct_api_push_blocked_wrap" class="mb-2 d-none"></div>';
|
||||
echo '<div id="ct_api_push_orphans_wrap" class="mb-2 d-none"></div>';
|
||||
echo '<div class="mb-2">';
|
||||
echo '<button type="button" class="btn btn-primary btn-sm" id="ct_api_btn_push" disabled>';
|
||||
echo '<i class="fa fa-cloud-upload" aria-hidden="true"></i> Pousser</button> ';
|
||||
echo '<i class="fa fa-cloud-upload" aria-hidden="true"></i> Pousser le différentiel</button> ';
|
||||
echo '<button type="button" class="btn btn-outline-secondary btn-sm" id="ct_api_btn_push_refresh">';
|
||||
echo '<i class="fa fa-refresh" aria-hidden="true"></i> Actualiser</button> ';
|
||||
echo '<button type="button" class="btn btn-outline-warning btn-sm" id="ct_api_btn_orphans" disabled>';
|
||||
echo '<i class="fa fa-unlink" aria-hidden="true"></i> <span id="ct_api_orphans_btn_label">Orphelines CT</span></button> ';
|
||||
echo '<button type="button" class="btn btn-outline-secondary btn-sm" id="ct_api_btn_probe_push">';
|
||||
echo '<i class="fa fa-flask" aria-hidden="true"></i> Sonde</button>';
|
||||
echo '<button type="button" class="btn btn-outline-info btn-sm" id="ct_api_btn_ct_count">';
|
||||
echo '<i class="fa fa-calculator" aria-hidden="true"></i> Nb ChronoTrack Live</button>';
|
||||
echo '</div>';
|
||||
echo '<div id="ct_api_push_result" class="small mt-2"></div>';
|
||||
echo '</div></div>';
|
||||
@ -258,14 +273,15 @@ function fxChronotrackApiAdminRenderDiagnosticPanel($intCtEventId) {
|
||||
echo 'Diagnostic API ChronoTrack <span class="text-muted small">(support — event ID ' . intval($intCtEventId) . ')</span>';
|
||||
echo '</a></div>';
|
||||
echo '<div class="collapse" id="ct_api_diagnostic_collapse"><div class="card-body">';
|
||||
echo '<p class="small text-muted mb-2">Utilise les credentials MS1 déjà configurés (OAuth en cache). ';
|
||||
echo 'Sonde <code>event/{id}/race</code>, <code>entry</code> (5 premières inscriptions), ';
|
||||
echo '<code>event</code> et <code>reg_choice</code>. Copie le rapport pour le développement.</p>';
|
||||
echo '<p class="small text-muted mb-2">Outils de support (credentials MS1 / OAuth en cache). '
|
||||
. 'Pas nécessaire pour une course en production.</p>';
|
||||
echo '<button type="button" class="btn btn-outline-dark btn-sm mb-2" id="ct_api_btn_diagnostic">';
|
||||
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 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 ».</div>';
|
||||
echo '<div id="ct_api_diagnostic_out" class="small text-muted">Cliquez sur « Lancer le diagnostic API » ou « Sonde ».</div>';
|
||||
echo '</div></div></div>';
|
||||
}
|
||||
|
||||
@ -285,16 +301,14 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
}
|
||||
?>
|
||||
<style>
|
||||
#ct_api_push_blocked_wrap .ct-api-blocked-table,
|
||||
#ct_api_push_orphans_wrap .ct-api-orphan-table { font-size: 0.8rem; }
|
||||
#ct_api_push_blocked_wrap .ct-api-blocked-table th,
|
||||
#ct_api_push_orphans_wrap .ct-api-orphan-table th { white-space: nowrap; }
|
||||
#ct_api_push_blocked_wrap .ct-api-blocked-scroll,
|
||||
#ct_api_push_orphans_wrap .ct-api-orphan-scroll { max-height: 55vh; overflow: auto; }
|
||||
#ct_api_push_blocked_wrap .ct-api-blocked-table { font-size: 0.8rem; }
|
||||
#ct_api_push_blocked_wrap .ct-api-blocked-table th { white-space: nowrap; }
|
||||
#ct_api_push_blocked_wrap .ct-api-blocked-scroll { max-height: 55vh; overflow: auto; }
|
||||
#ct_api_push_summary .ct-api-dash-table { font-size: 0.85rem; margin-bottom: 0.5rem; }
|
||||
#ct_api_push_summary .ct-api-dash-table th { background: #f8f9fa; font-weight: 600; white-space: nowrap; }
|
||||
#ct_api_push_summary .ct-api-dash-table td.num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
|
||||
#ct_api_push_summary .ct-api-dash-push { font-size: 0.85rem; margin-bottom: 0; }
|
||||
#ct_api_config_collapse .card { box-shadow: none; }
|
||||
</style>
|
||||
<?php fxChronotrackApiAdminRenderLoaderAssets($vDomaine); ?>
|
||||
<script>
|
||||
@ -309,6 +323,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
var blnInlineOnEventForm = <?php echo $blnInlineOnEventForm ? 'true' : 'false'; ?>;
|
||||
var xhrPushPreview = null;
|
||||
var blnPushPreviewLoaded = false;
|
||||
var intCtLiveCount = null;
|
||||
|
||||
function escHtml(str) {
|
||||
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
@ -684,6 +699,35 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
window.prompt('Copier le rapport (Ctrl+C) :', strText);
|
||||
});
|
||||
|
||||
$('#ct_api_btn_probe_push').on('click', function () {
|
||||
var $btn = $(this);
|
||||
var $out = $('#ct_api_diagnostic_out');
|
||||
$btn.prop('disabled', true);
|
||||
$out.html(waitHtml('Sonde POST (1 participant)…'));
|
||||
$('#ct_api_btn_diagnostic_copy').addClass('d-none');
|
||||
$.post(strAjaxUrl, { a: 'sync_probe_push', eve_id: intEveId }, function (res) {
|
||||
if (rejectIfSessionLost(res)) {
|
||||
return;
|
||||
}
|
||||
var strClass = (res && res.state === 'ok') ? 'text-success' : 'text-danger';
|
||||
var strHtml = '<div class="' + strClass + '"><strong>Sonde</strong> — '
|
||||
+ escHtml((res && res.message) ? res.message : 'Erreur') + '</div>';
|
||||
if (res && res.payload) {
|
||||
strHtml += '<pre class="small bg-light p-2 mt-2 mb-1" style="max-height:200px;overflow:auto;">'
|
||||
+ escHtml(JSON.stringify(res.payload, null, 2)) + '</pre>';
|
||||
}
|
||||
if (res && res.response_body) {
|
||||
strHtml += '<pre class="small bg-light p-2 mb-0" style="max-height:200px;overflow:auto;">'
|
||||
+ escHtml(res.response_body) + '</pre>';
|
||||
}
|
||||
$out.html(strHtml);
|
||||
}, 'json').fail(function () {
|
||||
$out.html('<div class="text-danger">Erreur réseau (sonde).</div>');
|
||||
}).always(function () {
|
||||
$btn.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
var objPushPreview = null;
|
||||
|
||||
function renderBlockedPanel(res) {
|
||||
@ -754,59 +798,6 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
$wrap.removeClass('d-none').html(html);
|
||||
}
|
||||
|
||||
function renderOrphansPanel(res) {
|
||||
var $wrap = $('#ct_api_push_orphans_wrap');
|
||||
if (!$wrap.length) {
|
||||
return;
|
||||
}
|
||||
if (!res || res.state !== 'ok') {
|
||||
$wrap.addClass('d-none').html('<div class="text-danger small">' + escHtml((res && res.message) ? res.message : 'Erreur') + '</div>');
|
||||
return;
|
||||
}
|
||||
var tabRows = res.orphans || [];
|
||||
if (!tabRows.length) {
|
||||
$wrap.addClass('d-none').empty();
|
||||
return;
|
||||
}
|
||||
|
||||
var html = '<div class="card border-warning">';
|
||||
html += '<div class="card-header py-2 bg-white d-flex justify-content-between align-items-center">';
|
||||
html += '<strong class="text-warning small">Orphelines CT — ' + tabRows.length + '</strong>';
|
||||
html += '<button type="button" class="btn btn-link btn-sm p-0" id="ct_api_btn_orphans_close">Fermer</button>';
|
||||
html += '</div><div class="card-body py-2">';
|
||||
html += '<div class="ct-api-orphan-scroll border rounded">';
|
||||
html += '<table class="table table-sm table-striped ct-api-orphan-table mb-0">';
|
||||
html += '<thead class="thead-light"><tr>';
|
||||
html += '<th>Nom</th><th>external_id</th><th>Dossard</th><th>Statut</th><th>entry_id</th><th>Raison</th>';
|
||||
html += '</tr></thead><tbody>';
|
||||
$.each(tabRows, function (_, row) {
|
||||
html += '<tr>';
|
||||
html += '<td>' + escHtml(row.name || '—') + '</td>';
|
||||
html += '<td>' + escHtml(row.external_id || '—') + '</td>';
|
||||
html += '<td>' + escHtml(row.bib ? ('#' + row.bib) : '—') + '</td>';
|
||||
html += '<td>' + escHtml(row.entry_status || '—') + '</td>';
|
||||
html += '<td><code class="small">' + escHtml(row.entry_id || '') + '</code></td>';
|
||||
html += '<td>' + escHtml(row.reason_label || row.reason || '') + '</td>';
|
||||
html += '</tr>';
|
||||
});
|
||||
html += '</tbody></table></div></div></div>';
|
||||
$wrap.removeClass('d-none').html(html);
|
||||
}
|
||||
|
||||
function updateOrphansButton(intCount, intCtTotal, intLinked) {
|
||||
var $btn = $('#ct_api_btn_orphans');
|
||||
var $lbl = $('#ct_api_orphans_btn_label');
|
||||
if (!$btn.length || !$lbl.length) {
|
||||
return;
|
||||
}
|
||||
var strLabel = 'Orphelines CT';
|
||||
if (intCount > 0) {
|
||||
strLabel += ' (' + intCount + ')';
|
||||
}
|
||||
$lbl.text(strLabel);
|
||||
$btn.prop('disabled', intCount <= 0 && intCtTotal <= intLinked);
|
||||
}
|
||||
|
||||
function renderPushPreview(res) {
|
||||
var $sum = $('#ct_api_push_summary');
|
||||
var $btn = $('#ct_api_btn_push');
|
||||
@ -817,8 +808,6 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
if (!res || res.state !== 'ok') {
|
||||
$sum.html('<div class="text-danger">' + escHtml((res && res.message) ? res.message : 'Erreur') + '</div>');
|
||||
$('#ct_api_push_blocked_wrap').addClass('d-none').empty();
|
||||
$('#ct_api_push_orphans_wrap').addClass('d-none').empty();
|
||||
updateOrphansButton(0, 0, 0);
|
||||
$btn.prop('disabled', true);
|
||||
return;
|
||||
}
|
||||
@ -829,34 +818,34 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
? res.pending_push
|
||||
: (res.to_push || 0);
|
||||
var intMs1 = res.ms1_total || 0;
|
||||
var intWillUpdate = res.will_update || res.already_in_ct || 0;
|
||||
var intWillCreate = res.will_create || res.to_create || 0;
|
||||
var intCtTotal = res.ct_total || 0;
|
||||
var intOrphans = res.ct_orphans || 0;
|
||||
var blnFirst = !!res.is_first_push;
|
||||
var strLastPush = res.last_push_at || null;
|
||||
|
||||
var html = '<table class="table table-sm table-bordered ct-api-dash-table mb-1">';
|
||||
html += '<thead><tr><th></th><th class="text-right">Total</th><th class="text-right">Sync OK</th><th class="text-right">Hors sync</th></tr></thead>';
|
||||
html += '<tbody>';
|
||||
html += '<tr><td>MS1</td>';
|
||||
html += '<thead><tr><th>MS1</th><th class="text-right">Total</th><th class="text-right">Éligibles</th>';
|
||||
html += '<th class="text-right">Exclus</th><th class="text-right">Différentiel</th></tr></thead><tbody>';
|
||||
html += '<tr><td>Participants</td>';
|
||||
html += '<td class="num">' + intMs1 + '</td>';
|
||||
html += '<td class="num text-primary">' + intEligible + '</td>';
|
||||
html += '<td class="num text-danger">' + intBlocked + '</td></tr>';
|
||||
html += '<tr><td>ChronoTrack</td>';
|
||||
html += '<td class="num">' + intCtTotal + '</td>';
|
||||
html += '<td class="num text-success">' + intWillUpdate + '</td>';
|
||||
html += '<td class="num text-warning">' + intOrphans + '</td></tr>';
|
||||
html += '<td class="num text-danger">' + intBlocked + '</td>';
|
||||
html += '<td class="num ' + (intPending > 0 ? 'text-warning' : 'text-success') + '">' + intPending + '</td></tr>';
|
||||
html += '</tbody></table>';
|
||||
|
||||
// MSIN-4328 — ligne « à repousser » (par_maj > last_push_at)
|
||||
if (intCtLiveCount !== null) {
|
||||
html += '<p class="ct-api-dash-push mb-1 text-muted">ChronoTrack Live : <strong>'
|
||||
+ intCtLiveCount + '</strong> inscription(s) '
|
||||
+ '<button type="button" class="btn btn-link btn-sm p-0 align-baseline" id="ct_api_btn_ct_count_inline">recompter</button></p>';
|
||||
} else {
|
||||
html += '<p class="ct-api-dash-push mb-1 text-muted">ChronoTrack Live : total non chargé '
|
||||
+ '(bouton <em>Nb ChronoTrack Live</em> — optionnel).</p>';
|
||||
}
|
||||
|
||||
if (intPending > 0) {
|
||||
html += '<p class="ct-api-dash-push mb-1"><strong>À pousser :</strong> '
|
||||
+ intPending + (blnFirst ? ' (premier envoi)' : ' modifié(s) depuis le dernier push')
|
||||
+ ' · ' + intEligible + ' éligibles · ' + intWillCreate + ' créer · ' + intWillUpdate + ' maj CT</p>';
|
||||
+ intPending + (blnFirst ? ' (premier envoi / baseline)' : ' modifié(s) depuis le dernier push')
|
||||
+ '</p>';
|
||||
} else {
|
||||
html += '<p class="ct-api-dash-push mb-1 text-success"><strong>À jour</strong> — '
|
||||
+ intEligible + ' éligibles, rien à repousser';
|
||||
html += '<p class="ct-api-dash-push mb-1 text-success"><strong>À jour</strong> — rien à repousser';
|
||||
if (strLastPush) {
|
||||
html += ' <span class="text-muted">(dernier push ' + escHtml(strLastPush) + ')</span>';
|
||||
}
|
||||
@ -865,8 +854,6 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
|
||||
$sum.html(html);
|
||||
renderBlockedPanel(res);
|
||||
updateOrphansButton(intOrphans, intCtTotal, intWillUpdate);
|
||||
$('#ct_api_push_orphans_wrap').addClass('d-none').empty();
|
||||
$btn.prop('disabled', intPending <= 0);
|
||||
}
|
||||
|
||||
@ -903,12 +890,12 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
}
|
||||
xhrPushPreview.abort();
|
||||
}
|
||||
$sum.html(waitHtml('Calcul du diff MS1 ↔ ChronoTrack…'));
|
||||
$sum.html(waitHtml('Lecture MS1 (éligibles / différentiel)…'));
|
||||
xhrPushPreview = $.ajax({
|
||||
url: strAjaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
timeout: 180000,
|
||||
timeout: 60000,
|
||||
data: { a: 'sync_preview', eve_id: intEveId }
|
||||
}).done(function (res) {
|
||||
if (rejectIfSessionLost(res)) {
|
||||
@ -921,7 +908,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
return;
|
||||
}
|
||||
var strMsg = (strStatus === 'timeout')
|
||||
? 'Délai dépassé — ChronoTrack met trop de temps à répondre. Réessayez avec Actualiser.'
|
||||
? 'Délai dépassé — réessayez Actualiser.'
|
||||
: 'Erreur réseau (preview).';
|
||||
$sum.html('<div class="text-danger">' + escHtml(strMsg) + '</div>');
|
||||
}).always(function () {
|
||||
@ -929,6 +916,46 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
});
|
||||
}
|
||||
|
||||
function loadCtLiveCount() {
|
||||
var $btn = $('#ct_api_btn_ct_count');
|
||||
var $sum = $('#ct_api_push_summary');
|
||||
$btn.prop('disabled', true);
|
||||
if ($sum.length && objPushPreview && objPushPreview.state === 'ok') {
|
||||
// conserve le tableau MS1, indique le chargement CT
|
||||
$sum.find('.ct-api-dash-push').first().replaceWith(
|
||||
'<p class="ct-api-dash-push mb-1 text-muted">' + waitHtml('Comptage ChronoTrack Live…') + '</p>'
|
||||
);
|
||||
}
|
||||
$.ajax({
|
||||
url: strAjaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
timeout: 180000,
|
||||
data: { a: 'sync_ct_count', eve_id: intEveId }
|
||||
}).done(function (res) {
|
||||
if (rejectIfSessionLost(res)) {
|
||||
return;
|
||||
}
|
||||
if (!res || res.state !== 'ok') {
|
||||
window.alert((res && res.message) ? res.message : 'Impossible de compter sur ChronoTrack Live');
|
||||
return;
|
||||
}
|
||||
intCtLiveCount = parseInt(res.ct_total, 10);
|
||||
if (isNaN(intCtLiveCount)) {
|
||||
intCtLiveCount = null;
|
||||
}
|
||||
if (objPushPreview && objPushPreview.state === 'ok') {
|
||||
renderPushPreview(objPushPreview);
|
||||
}
|
||||
}).fail(function (jqXHR, strStatus) {
|
||||
window.alert(strStatus === 'timeout'
|
||||
? 'Délai dépassé pour le comptage ChronoTrack Live.'
|
||||
: 'Erreur réseau (comptage CT).');
|
||||
}).always(function () {
|
||||
$btn.prop('disabled', false);
|
||||
});
|
||||
}
|
||||
|
||||
function maybeLoadPushPreviewOnReveal() {
|
||||
if (!$('#ct_api_push_card').length) {
|
||||
return;
|
||||
@ -948,66 +975,14 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
$('#ct_api_btn_push_refresh').on('click', function () {
|
||||
loadPushPreview(true);
|
||||
$('#ct_api_push_result').empty();
|
||||
$('#ct_api_push_orphans_wrap').addClass('d-none').empty();
|
||||
});
|
||||
|
||||
$('#ct_api_btn_orphans').on('click', function () {
|
||||
var $btn = $(this);
|
||||
var $wrap = $('#ct_api_push_orphans_wrap');
|
||||
$btn.prop('disabled', true);
|
||||
$wrap.removeClass('d-none').html(waitHtml('Chargement orphelines…'));
|
||||
$.post(strAjaxUrl, { a: 'sync_orphans', eve_id: intEveId }, function (res) {
|
||||
if (rejectIfSessionLost(res)) {
|
||||
return;
|
||||
}
|
||||
renderOrphansPanel(res);
|
||||
if (res && res.state === 'ok') {
|
||||
updateOrphansButton(res.count || 0, res.ct_total || 0, res.linked_count || 0);
|
||||
}
|
||||
}, 'json').fail(function () {
|
||||
$wrap.html('<div class="text-danger small">Erreur réseau.</div>');
|
||||
}).always(function () {
|
||||
if (objPushPreview && objPushPreview.state === 'ok') {
|
||||
updateOrphansButton(
|
||||
objPushPreview.ct_orphans || 0,
|
||||
objPushPreview.ct_total || 0,
|
||||
objPushPreview.already_in_ct || 0
|
||||
);
|
||||
} else {
|
||||
$btn.prop('disabled', false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '#ct_api_btn_orphans_close', function () {
|
||||
$('#ct_api_push_orphans_wrap').addClass('d-none').empty();
|
||||
});
|
||||
|
||||
$('#ct_api_btn_probe_push').on('click', function () {
|
||||
var $btn = $(this);
|
||||
var $res = $('#ct_api_push_result');
|
||||
$btn.prop('disabled', true);
|
||||
$res.html(waitHtml('Sonde POST (1 participant)…'));
|
||||
$.post(strAjaxUrl, { a: 'sync_probe_push', eve_id: intEveId }, function (res) {
|
||||
if (rejectIfSessionLost(res)) {
|
||||
return;
|
||||
}
|
||||
var strClass = (res && res.state === 'ok') ? 'text-success' : 'text-danger';
|
||||
var strHtml = '<div class="' + strClass + '"><strong>Sonde</strong> — ' + escHtml((res && res.message) ? res.message : 'Erreur') + '</div>';
|
||||
if (res && res.payload) {
|
||||
strHtml += '<pre class="small bg-light p-2 mt-2 mb-1" style="max-height:200px;overflow:auto;">'
|
||||
+ escHtml(JSON.stringify(res.payload, null, 2)) + '</pre>';
|
||||
}
|
||||
if (res && res.response_body) {
|
||||
strHtml += '<pre class="small bg-light p-2 mb-0" style="max-height:200px;overflow:auto;">'
|
||||
+ escHtml(res.response_body) + '</pre>';
|
||||
}
|
||||
$res.html(strHtml);
|
||||
}, 'json').fail(function () {
|
||||
$res.html('<div class="text-danger">Erreur réseau (sonde).</div>');
|
||||
}).always(function () {
|
||||
$btn.prop('disabled', false);
|
||||
$('#ct_api_btn_ct_count').on('click', function () {
|
||||
loadCtLiveCount();
|
||||
});
|
||||
$(document).on('click', '#ct_api_btn_ct_count_inline', function (e) {
|
||||
e.preventDefault();
|
||||
loadCtLiveCount();
|
||||
});
|
||||
|
||||
$('#ct_api_btn_push').on('click', function () {
|
||||
@ -1019,7 +994,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
intPending = objPushPreview.to_push || objPushPreview.transferable || 0;
|
||||
}
|
||||
}
|
||||
var strConfirm = 'Pousser ' + intPending + ' participant(s) vers ChronoTrack ?';
|
||||
var strConfirm = 'Pousser le différentiel : ' + intPending + ' participant(s) vers ChronoTrack ?';
|
||||
if (!window.confirm(strConfirm)) {
|
||||
return;
|
||||
}
|
||||
@ -1036,7 +1011,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
var blnFatal = false;
|
||||
|
||||
$btn.prop('disabled', true);
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_orphans, #ct_api_btn_probe_push').prop('disabled', true);
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_ct_count').prop('disabled', true);
|
||||
|
||||
function renderProgress(intDone, intTot, strExtra) {
|
||||
var intPct = (intTot > 0) ? Math.min(100, Math.round((intDone * 100) / intTot)) : 0;
|
||||
@ -1058,7 +1033,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
}
|
||||
|
||||
function finishPush() {
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_orphans, #ct_api_btn_probe_push').prop('disabled', false);
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_ct_count').prop('disabled', false);
|
||||
$btn.prop('disabled', false);
|
||||
var strFinal = (intErrSum === 0)
|
||||
? (intOkSum + ' participant(s) synchronisé(s) (POST ' + intPostSum + ' · PUT ' + intPutSum + ').')
|
||||
@ -1093,7 +1068,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
if (!res || (res.state !== 'ok' && res.state !== 'partial')) {
|
||||
if (res && res.total === 0) {
|
||||
renderPushResult(res);
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_orphans, #ct_api_btn_probe_push').prop('disabled', false);
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_ct_count').prop('disabled', false);
|
||||
$btn.prop('disabled', false);
|
||||
return;
|
||||
}
|
||||
@ -1155,7 +1130,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
}
|
||||
if (!prep || prep.state !== 'ok') {
|
||||
renderPushResult(prep || { state: 'error', message: 'Préparation échouée' });
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_orphans, #ct_api_btn_probe_push').prop('disabled', false);
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_ct_count').prop('disabled', false);
|
||||
$btn.prop('disabled', false);
|
||||
return;
|
||||
}
|
||||
@ -1173,7 +1148,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
}
|
||||
strMsg += '. Vérifiez que le code MSIN-4328 est déployé (action sync_push_prepare).';
|
||||
renderPushResult({ state: 'error', message: strMsg });
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_orphans, #ct_api_btn_probe_push').prop('disabled', false);
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_ct_count').prop('disabled', false);
|
||||
$btn.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -978,45 +978,9 @@ function fxChronotrackApiSyncBuildPreview($intEveId) {
|
||||
|
||||
$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);
|
||||
$intAlreadyInCt = 0;
|
||||
$intToCreate = 0;
|
||||
|
||||
foreach ($tabEligibleKeys as $strExt => $blnTrue) {
|
||||
if (isset($tabCtEntries[$strExt])) {
|
||||
$intAlreadyInCt++;
|
||||
} else {
|
||||
$intToCreate++;
|
||||
}
|
||||
}
|
||||
|
||||
$intCtOrphans = 0;
|
||||
if (!empty($arrCtFetch['entities']) && is_array($arrCtFetch['entities'])) {
|
||||
$tabOrphanRows = fxChronotrackApiSyncComputeCtOrphanRows($arrCtFetch['entities'], $tabEligibleKeys);
|
||||
$intCtOrphans = count($tabOrphanRows);
|
||||
} else {
|
||||
foreach ($tabCtEntries as $strExt => $blnEntity) {
|
||||
if (!isset($tabEligibleKeys[$strExt])) {
|
||||
$intCtOrphans++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
@ -1041,13 +1005,14 @@ function fxChronotrackApiSyncBuildPreview($intEveId) {
|
||||
'anomaly_duplicate_bib' => $arrClass['blocked_duplicate_bib'],
|
||||
'duplicate_bib_numbers' => $arrClass['duplicate_bib_numbers'],
|
||||
'anomalies' => $arrClass['blocked'],
|
||||
'ct_total' => $intCtTotal,
|
||||
'already_in_ct' => $intAlreadyInCt,
|
||||
'will_update' => $intAlreadyInCt,
|
||||
'to_create' => $intToCreate,
|
||||
'will_create' => $intToCreate,
|
||||
'ct_orphans' => $intCtOrphans,
|
||||
'ct_without_external_id' => $intCtWithoutExt,
|
||||
'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,
|
||||
'last_push_at' => ($strLastPushAt !== '' && $strLastPushAt !== '0000-00-00 00:00:00')
|
||||
@ -1058,6 +1023,101 @@ function fxChronotrackApiSyncBuildPreview($intEveId) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MSIN-4328 — total entries CT Live (sans construire le diff MS1↔CT).
|
||||
* Essaie d’abord un total dans la réponse API ; sinon compte par pagination légère.
|
||||
*/
|
||||
function fxChronotrackApiSyncExtractCtListTotal($mixJson) {
|
||||
if (!is_array($mixJson)) {
|
||||
return null;
|
||||
}
|
||||
$tabKeys = array(
|
||||
'total', 'total_count', 'totalCount', 'count', 'entry_count', 'num_results',
|
||||
'results_count', 'nb_results', 'total_entries',
|
||||
);
|
||||
foreach ($tabKeys as $strKey) {
|
||||
if (isset($mixJson[$strKey]) && is_numeric($mixJson[$strKey])) {
|
||||
$intVal = intval($mixJson[$strKey]);
|
||||
if ($intVal >= 0) {
|
||||
return $intVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (array('pagination', 'page', 'meta', 'paging') as $strNest) {
|
||||
if (!isset($mixJson[$strNest]) || !is_array($mixJson[$strNest])) {
|
||||
continue;
|
||||
}
|
||||
$intNested = fxChronotrackApiSyncExtractCtListTotal($mixJson[$strNest]);
|
||||
if ($intNested !== null) {
|
||||
return $intNested;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function fxChronotrackApiSyncFetchCtEntryCount($intCtEventId) {
|
||||
$intCtEventId = intval($intCtEventId);
|
||||
if ($intCtEventId <= 0) {
|
||||
return array('state' => 'error', 'message' => 'ct_event_id manquant', 'ct_total' => null);
|
||||
}
|
||||
|
||||
// 1) Une page minimale — si CT renvoie un total, on s’arrête là.
|
||||
$arrApi = fxChronotrackApiOAuthApiGet('event/' . $intCtEventId . '/entry', array(
|
||||
'page' => 1,
|
||||
'page_size' => 1,
|
||||
));
|
||||
if ($arrApi['state'] !== 'ok') {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => $arrApi['message'] ?? 'Erreur lecture entries CT',
|
||||
'ct_total' => null,
|
||||
);
|
||||
}
|
||||
|
||||
$intFromMeta = fxChronotrackApiSyncExtractCtListTotal($arrApi['json'] ?? null);
|
||||
if ($intFromMeta !== null) {
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'message' => 'Total ChronoTrack Live (métadonnée API)',
|
||||
'ct_total' => $intFromMeta,
|
||||
'via' => 'meta',
|
||||
);
|
||||
}
|
||||
|
||||
// 2) Sinon compte page par page (gros page_size, on ne garde que le count).
|
||||
$intTotal = 0;
|
||||
$intPage = 1;
|
||||
$intPageSize = 250;
|
||||
$intMaxPages = 200;
|
||||
while ($intPage <= $intMaxPages) {
|
||||
$arrPage = fxChronotrackApiOAuthApiGet('event/' . $intCtEventId . '/entry', array(
|
||||
'page' => $intPage,
|
||||
'page_size' => $intPageSize,
|
||||
));
|
||||
if ($arrPage['state'] !== 'ok') {
|
||||
return array(
|
||||
'state' => 'error',
|
||||
'message' => $arrPage['message'] ?? 'Erreur lecture entries CT',
|
||||
'ct_total' => ($intTotal > 0) ? $intTotal : null,
|
||||
);
|
||||
}
|
||||
$arrEntities = fxChronotrackApiNormalizeEntityList($arrPage['json'], 'entry');
|
||||
$intN = count($arrEntities);
|
||||
$intTotal += $intN;
|
||||
if ($intN < $intPageSize) {
|
||||
break;
|
||||
}
|
||||
$intPage++;
|
||||
}
|
||||
|
||||
return array(
|
||||
'state' => 'ok',
|
||||
'message' => 'Total ChronoTrack Live (comptage paginé)',
|
||||
'ct_total' => $intTotal,
|
||||
'via' => 'pages',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stockage job push (fichier temp) — évite de saturer $_SESSION sur ~2000 entries. MSIN-4328
|
||||
*/
|
||||
@ -1149,20 +1209,11 @@ function fxChronotrackApiSyncPushPrepare($intEveId) {
|
||||
$strLastPushAt = trim((string)($arrConfig['last_push_at'] ?? ''));
|
||||
$tabPending = fxChronotrackApiSyncFilterChangedSincePush($arrClass['transferable'], $strLastPushAt);
|
||||
|
||||
$tabCtEntries = array();
|
||||
$arrCtFetch = fxChronotrackApiSyncFetchCtEntriesIndexed($intCtEventId);
|
||||
if ($arrCtFetch['state'] === 'ok') {
|
||||
$tabCtEntries = $arrCtFetch['entries'];
|
||||
}
|
||||
|
||||
// Plus de scan CT à la préparation : POST upsert (crée ou maj) sans entry_id.
|
||||
$tabBatch = array();
|
||||
$tabMeta = array();
|
||||
foreach ($tabPending as $arrItem) {
|
||||
$arrPayload = fxChronotrackApiSyncEnrichPayloadWithCtEntry(
|
||||
$arrItem['payload'],
|
||||
$tabCtEntries[$arrItem['payload']['external_id']] ?? null
|
||||
);
|
||||
$tabBatch[] = $arrPayload;
|
||||
$tabBatch[] = fxChronotrackApiSyncPayloadForPost($arrItem['payload']);
|
||||
$tabMeta[] = array(
|
||||
'par_id' => intval($arrItem['row']['par_id'] ?? 0),
|
||||
'external_id' => $arrItem['payload']['external_id'],
|
||||
|
||||
@ -91,10 +91,25 @@ switch ($strAction) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
|
||||
break;
|
||||
}
|
||||
set_time_limit(180);
|
||||
set_time_limit(60);
|
||||
$tabRetour = fxChronotrackApiSyncBuildPreview($intEveId);
|
||||
break;
|
||||
|
||||
case 'sync_ct_count':
|
||||
if ($intEveId <= 0) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
|
||||
break;
|
||||
}
|
||||
$arrCfg = fxChronotrackApiConfigGet($intEveId);
|
||||
$intCtEventId = intval($arrCfg['ct_event_id'] ?? 0);
|
||||
if ($intCtEventId <= 0) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'Événement non lié à ChronoTrack');
|
||||
break;
|
||||
}
|
||||
set_time_limit(180);
|
||||
$tabRetour = fxChronotrackApiSyncFetchCtEntryCount($intCtEventId);
|
||||
break;
|
||||
|
||||
case 'sync_probe_push':
|
||||
if ($intEveId <= 0) {
|
||||
$tabRetour = array('state' => 'error', 'message' => 'eve_id manquant');
|
||||
|
||||
Reference in New Issue
Block a user