MSIN-4574 — Enhance ChronoTrack API synchronization by implementing retry logic for HTTP requests, adjusting batch sizes for entries, and improving error handling. Update version code to 4.72.974 to reflect these changes.
This commit is contained in:
@ -1799,9 +1799,45 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
});
|
||||
|
||||
// MSIN-4574 — vérifier alignement MS1 ↔ CT (orphelins)
|
||||
function setReconcileStatus(strTitle, strHint, strKind) {
|
||||
var $status = $('#ct_api_push_status');
|
||||
if (!$status.length) {
|
||||
return;
|
||||
}
|
||||
strKind = strKind || 'loading';
|
||||
var strColor = (strKind === 'ok') ? 'text-success'
|
||||
: ((strKind === 'error') ? 'text-danger'
|
||||
: ((strKind === 'todo') ? 'text-warning' : 'text-primary'));
|
||||
$status.removeClass('ct-api-push-status--ok ct-api-push-status--todo ct-api-push-status--loading');
|
||||
if (strKind === 'loading') {
|
||||
$status.addClass('ct-api-push-status--loading');
|
||||
} else if (strKind === 'ok') {
|
||||
$status.addClass('ct-api-push-status--ok');
|
||||
} else if (strKind === 'todo') {
|
||||
$status.addClass('ct-api-push-status--todo');
|
||||
}
|
||||
$status.html(
|
||||
'<p class="ct-api-push-status__title ' + strColor + ' mb-1">'
|
||||
+ (strKind === 'loading'
|
||||
? '<i class="fa fa-balance-scale" aria-hidden="true"></i> '
|
||||
: '')
|
||||
+ escHtml(strTitle) + '</p>'
|
||||
+ '<p class="ct-api-push-status__hint mb-0">' + escHtml(strHint || '') + '</p>'
|
||||
);
|
||||
try {
|
||||
if ($status[0] && $status[0].scrollIntoView) {
|
||||
$status[0].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
} catch (eScroll) { /* ignore */ }
|
||||
}
|
||||
|
||||
function renderReconcilePanel(res) {
|
||||
var $wrap = $('#ct_api_reconcile_wrap');
|
||||
var intCount = (res && res.orphans) ? res.orphans.length : (res.count || 0);
|
||||
var intCount = (res && typeof res.count !== 'undefined')
|
||||
? parseInt(res.count, 10)
|
||||
: ((res && res.orphans) ? res.orphans.length : 0);
|
||||
var tabRows = (res && res.orphans) ? res.orphans : [];
|
||||
var intShowMax = 80;
|
||||
var html = '<div class="border rounded p-2 bg-light">';
|
||||
html += '<div class="d-flex align-items-center justify-content-between mb-2">';
|
||||
html += '<strong>Alignement ChronoTrack</strong>';
|
||||
@ -1813,23 +1849,35 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
html += '<div class="text-danger small">' + escHtml((res && res.message) ? res.message : 'Erreur') + '</div>';
|
||||
html += '</div>';
|
||||
$wrap.removeClass('d-none').html(html);
|
||||
setReconcileStatus(
|
||||
'Alignement — erreur',
|
||||
(res && res.message) ? res.message : 'Erreur',
|
||||
'error'
|
||||
);
|
||||
return;
|
||||
}
|
||||
html += '<p class="mb-2"><span class="badge badge-info">' + intCount + '</span> orphelin(s)';
|
||||
if (typeof res.ct_total !== 'undefined' && res.ct_total !== null) {
|
||||
html += ' · CT total ' + escHtml(String(res.ct_total));
|
||||
}
|
||||
if (typeof res.eligible_count !== 'undefined') {
|
||||
html += ' · éligibles MS1 ' + escHtml(String(res.eligible_count));
|
||||
}
|
||||
if (res.elapsed_ms) {
|
||||
html += ' · ' + escHtml(String(Math.round(res.elapsed_ms / 100) / 10)) + ' s';
|
||||
}
|
||||
html += '</p>';
|
||||
if (intCount === 0) {
|
||||
html += '<div class="text-success small mb-0">Aucun écart détecté — MS1 et ChronoTrack sont alignés côté entries.</div>';
|
||||
html += '</div>';
|
||||
$wrap.removeClass('d-none').html(html);
|
||||
setReconcileStatus('Alignement OK', 'Aucun orphelin CT détecté.', 'ok');
|
||||
return;
|
||||
}
|
||||
html += '<div class="table-responsive mb-2" style="max-height:280px;overflow:auto;">';
|
||||
html += '<table class="table table-sm table-striped mb-0"><thead><tr>';
|
||||
html += '<th>Nom CT</th><th>ext</th><th>bib</th><th>statut</th><th>raison</th></tr></thead><tbody>';
|
||||
$.each(res.orphans || [], function (_, row) {
|
||||
$.each(tabRows.slice(0, intShowMax), function (_, row) {
|
||||
html += '<tr>';
|
||||
html += '<td>' + escHtml(row.name || '—') + '</td>';
|
||||
html += '<td><code>' + escHtml(row.external_id || '—') + '</code></td>';
|
||||
@ -1839,24 +1887,41 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
html += '</tr>';
|
||||
});
|
||||
html += '</tbody></table></div>';
|
||||
if (tabRows.length > intShowMax) {
|
||||
html += '<p class="small text-muted">Affichage des ' + intShowMax
|
||||
+ ' premiers — total ' + intCount + '. Le retrait traite toute la liste.</p>';
|
||||
}
|
||||
html += '<button type="button" class="btn btn-warning btn-sm" id="ct_api_btn_reconcile_withdraw">';
|
||||
html += '<i class="fa fa-user-times" aria-hidden="true"></i> Retirer orphelins (WITHDRAWN + clear bib)</button>';
|
||||
html += '<p class="small text-muted mt-2 mb-0">Action : statut WITHDRAWN + dossard vidé sur ChronoTrack '
|
||||
+ '(hors_eligible / doublons). Les entries sans external ID sont listées mais non retirées automatiquement.</p>';
|
||||
html += '</div>';
|
||||
$wrap.removeClass('d-none').html(html);
|
||||
setReconcileStatus(
|
||||
'Alignement — ' + intCount + ' orphelin(s)',
|
||||
(res.message || '') + ' Détail sous les boutons.',
|
||||
'todo'
|
||||
);
|
||||
}
|
||||
|
||||
function loadReconcile() {
|
||||
var $btn = $('#ct_api_btn_reconcile');
|
||||
var $wrap = $('#ct_api_reconcile_wrap');
|
||||
$btn.prop('disabled', true);
|
||||
$wrap.removeClass('d-none').html('<span class="text-muted">Analyse alignement ChronoTrack…</span>');
|
||||
$wrap.removeClass('d-none').html(
|
||||
'<span class="text-muted"><i class="fa fa-spinner fa-spin" aria-hidden="true"></i> '
|
||||
+ 'Analyse alignement ChronoTrack (lecture entries CT)…</span>'
|
||||
);
|
||||
setReconcileStatus(
|
||||
'Alignement en cours…',
|
||||
'Lecture des entries ChronoTrack — ne fermez pas la page.',
|
||||
'loading'
|
||||
);
|
||||
$.ajax({
|
||||
url: strAjaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
timeout: 180000,
|
||||
timeout: 240000,
|
||||
data: { a: 'sync_orphans', eve_id: intEveId }
|
||||
}).done(function (res) {
|
||||
if (rejectIfSessionLost(res)) {
|
||||
@ -1864,12 +1929,16 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
}
|
||||
renderReconcilePanel(res);
|
||||
}).fail(function (jqXHR, strStatus) {
|
||||
renderReconcilePanel({
|
||||
state: 'error',
|
||||
message: strStatus === 'timeout'
|
||||
? 'Délai dépassé (lecture entries CT).'
|
||||
: 'Erreur réseau (alignement).'
|
||||
});
|
||||
var intHttp = (jqXHR && jqXHR.status) ? parseInt(jqXHR.status, 10) : 0;
|
||||
var strMsg;
|
||||
if (strStatus === 'timeout') {
|
||||
strMsg = 'Délai dépassé (lecture entries CT trop longue). Réessayez.';
|
||||
} else if (intHttp === 503 || intHttp === 502) {
|
||||
strMsg = 'HTTP ' + intHttp + ' serveur MS1 pendant l’alignement — réessayez.';
|
||||
} else {
|
||||
strMsg = 'Erreur réseau (alignement' + (intHttp ? ', HTTP ' + intHttp : '') + ').';
|
||||
}
|
||||
renderReconcilePanel({ state: 'error', message: strMsg });
|
||||
}).always(function () {
|
||||
$btn.prop('disabled', false);
|
||||
});
|
||||
@ -1896,6 +1965,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
}
|
||||
var $btn = $(this);
|
||||
$btn.prop('disabled', true).text('Retrait en cours…');
|
||||
setReconcileStatus('Retrait orphelins…', 'WITHDRAWN + clear bib sur ChronoTrack.', 'loading');
|
||||
$.ajax({
|
||||
url: strAjaxUrl,
|
||||
type: 'POST',
|
||||
@ -1911,6 +1981,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
loadPushPreview(true);
|
||||
}).fail(function () {
|
||||
window.alert('Erreur réseau (retrait orphelins).');
|
||||
setReconcileStatus('Retrait interrompu', 'Erreur réseau.', 'error');
|
||||
}).always(function () {
|
||||
$btn.prop('disabled', false);
|
||||
});
|
||||
@ -2139,19 +2210,29 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
}
|
||||
}
|
||||
|
||||
function finishPush() {
|
||||
function finishPush(strModeFinish) {
|
||||
stopElapsedTimer();
|
||||
blnPushRunning = false;
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_ct_count, #ct_api_btn_logs, #ct_api_btn_reconcile')
|
||||
$('#ct_api_btn_push, #ct_api_btn_push_full, #ct_api_btn_push_refresh, #ct_api_btn_ct_count, #ct_api_btn_logs, #ct_api_btn_reconcile')
|
||||
.prop('disabled', false);
|
||||
var strFinal = (intErrSum === 0)
|
||||
? (intOkSum + ' participant(s) synchronisé(s) (POST ' + intPostSum + ' · PUT ' + intPutSum + ').')
|
||||
: (intOkSum + ' OK, ' + intErrSum + ' erreur(s).');
|
||||
if (blnForceFull && intErrSum === 0) {
|
||||
strFinal = 'Resync complète — ' + strFinal;
|
||||
var strMode = strModeFinish || (blnFatal ? 'interrupted' : ((intErrSum === 0) ? 'ok' : 'partial'));
|
||||
var strFinal;
|
||||
if (strMode === 'interrupted') {
|
||||
strFinal = 'Interrompu après ' + intOkSum + ' OK'
|
||||
+ (intErrSum ? (', ' + intErrSum + ' erreur(s)') : '')
|
||||
+ ' (POST ' + intPostSum + '). Relancer Tout renvoyer pour reprendre le reste.';
|
||||
} else if (intErrSum === 0) {
|
||||
strFinal = intOkSum + ' participant(s) synchronisé(s) (POST ' + intPostSum
|
||||
+ ' · dossards ' + intPutSum + ').';
|
||||
if (blnForceFull) {
|
||||
strFinal = 'Resync complète — ' + strFinal;
|
||||
}
|
||||
} else {
|
||||
strFinal = intOkSum + ' OK, ' + intErrSum + ' erreur(s).';
|
||||
}
|
||||
strFinal += ' · durée ' + formatElapsed();
|
||||
var strState = blnFatal ? 'error' : ((intErrSum === 0) ? 'ok' : 'partial');
|
||||
var strState = (strMode === 'interrupted') ? 'error'
|
||||
: ((intErrSum === 0) ? 'ok' : 'partial');
|
||||
renderPushResult({
|
||||
state: strState,
|
||||
message: strFinal,
|
||||
@ -2175,19 +2256,79 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
loadPushPreview(true);
|
||||
}
|
||||
|
||||
// MSIN-4574 — passe dossards découpée (après identité)
|
||||
function pushBibChunk(intBibPendingHint, intBibChunk) {
|
||||
intBibChunk = intBibChunk || 20;
|
||||
var intLeft = (intBibPendingHint != null) ? parseInt(intBibPendingHint, 10) : 0;
|
||||
renderProgress(
|
||||
Math.min(intOffset, intTotal),
|
||||
intTotal,
|
||||
'passe dossards — reste ~' + intLeft + '…'
|
||||
);
|
||||
$.ajax({
|
||||
url: strAjaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
timeout: 180000,
|
||||
data: {
|
||||
a: 'sync_push_bibs',
|
||||
eve_id: intEveId,
|
||||
limit: intBibChunk
|
||||
}
|
||||
}).done(function (res) {
|
||||
if (rejectIfSessionLost(res)) {
|
||||
stopElapsedTimer();
|
||||
return;
|
||||
}
|
||||
if (!res) {
|
||||
blnFatal = true;
|
||||
tabErrAll.push('Réponse vide (passe dossards)');
|
||||
finishPush('interrupted');
|
||||
return;
|
||||
}
|
||||
intOkSum += parseInt(res.pushed_ok || 0, 10);
|
||||
intErrSum += parseInt(res.pushed_error || 0, 10);
|
||||
intPutSum += parseInt(res.pushed_ok || 0, 10);
|
||||
if (res.errors && res.errors.length) {
|
||||
$.each(res.errors, function (_, msg) {
|
||||
if (tabErrAll.length < 20) {
|
||||
tabErrAll.push(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
var intPend = parseInt(res.bib_pending != null ? res.bib_pending : 0, 10);
|
||||
if (res.done || intPend <= 0) {
|
||||
finishPush(intErrSum === 0 ? 'ok' : 'partial');
|
||||
return;
|
||||
}
|
||||
window.setTimeout(function () {
|
||||
pushBibChunk(intPend, intBibChunk);
|
||||
}, 50);
|
||||
}).fail(function (jqXHR, strStatus) {
|
||||
blnFatal = true;
|
||||
var intHttp = (jqXHR && jqXHR.status) ? parseInt(jqXHR.status, 10) : 0;
|
||||
var strDetail = (strStatus === 'timeout')
|
||||
? 'Délai dépassé sur passe dossards — relancer Tout renvoyer.'
|
||||
: ('Interruption passe dossards (' + strStatus + ', HTTP ' + intHttp + '). Relancer.');
|
||||
tabErrAll.push(strDetail);
|
||||
finishPush('interrupted');
|
||||
});
|
||||
}
|
||||
|
||||
function pushChunk() {
|
||||
var intLot = Math.floor(intOffset / Math.max(1, intChunk)) + 1;
|
||||
var intLots = Math.max(1, Math.ceil(intTotal / Math.max(1, intChunk)));
|
||||
renderProgress(
|
||||
intOffset,
|
||||
intTotal,
|
||||
'lot ' + intLot + '/' + intLots + ' (POST ' + Math.min(intChunk, Math.max(0, intTotal - intOffset)) + ')…'
|
||||
'identité lot ' + intLot + '/' + intLots
|
||||
+ ' (POST ' + Math.min(intChunk, Math.max(0, intTotal - intOffset)) + ')…'
|
||||
);
|
||||
$.ajax({
|
||||
url: strAjaxUrl,
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
timeout: 300000,
|
||||
timeout: 180000,
|
||||
data: {
|
||||
a: 'sync_push',
|
||||
eve_id: intEveId,
|
||||
@ -2203,7 +2344,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
if (!res) {
|
||||
blnFatal = true;
|
||||
tabErrAll.push('Réponse vide du serveur');
|
||||
finishPush();
|
||||
finishPush('interrupted');
|
||||
return;
|
||||
}
|
||||
if (res.state !== 'ok' && res.state !== 'partial' && res.state !== 'error') {
|
||||
@ -2211,13 +2352,13 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
if (res.message) {
|
||||
tabErrAll.push(res.message);
|
||||
}
|
||||
finishPush();
|
||||
finishPush('interrupted');
|
||||
return;
|
||||
}
|
||||
if (res.total === 0 && res.state === 'error') {
|
||||
stopElapsedTimer();
|
||||
renderPushResult(res);
|
||||
$('#ct_api_btn_push_refresh, #ct_api_btn_ct_count, #ct_api_btn_logs, #ct_api_btn_reconcile')
|
||||
$('#ct_api_btn_push, #ct_api_btn_push_full, #ct_api_btn_push_refresh, #ct_api_btn_ct_count, #ct_api_btn_logs, #ct_api_btn_reconcile')
|
||||
.prop('disabled', false);
|
||||
blnPushRunning = false;
|
||||
loadPushPreview(true);
|
||||
@ -2244,32 +2385,52 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed
|
||||
renderProgress(
|
||||
Math.min(intOffset, intTotal),
|
||||
intTotal,
|
||||
'lot ' + Math.ceil(intOffset / Math.max(1, intChunk)) + ' terminé'
|
||||
'lot identité terminé'
|
||||
);
|
||||
if (res.done || intOffset >= intTotal) {
|
||||
finishPush();
|
||||
|
||||
// Identité finie → passe dossards séparée (évite 503 hébergeur)
|
||||
if (res.identity_done || res.phase === 'bibs') {
|
||||
var intBibPend = parseInt(res.bib_pending != null ? res.bib_pending : 0, 10);
|
||||
var intBibChunk = parseInt(res.bib_chunk_size != null ? res.bib_chunk_size : 20, 10);
|
||||
if (intBibPend > 0) {
|
||||
window.setTimeout(function () {
|
||||
pushBibChunk(intBibPend, intBibChunk);
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
finishPush(intErrSum === 0 ? 'ok' : 'partial');
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.done || intOffset >= intTotal) {
|
||||
finishPush(intErrSum === 0 ? 'ok' : 'partial');
|
||||
return;
|
||||
}
|
||||
// Enchaîner tout de suite (pas de pause artificielle — CT + retry 503 suffisent)
|
||||
pushChunk();
|
||||
}).fail(function (jqXHR, strStatus) {
|
||||
blnFatal = true;
|
||||
var intHttp = (jqXHR && jqXHR.status) ? parseInt(jqXHR.status, 10) : 0;
|
||||
var strDetail;
|
||||
if (strStatus === 'timeout') {
|
||||
strDetail = 'Délai dépassé sur un lot — relancer (lots plus petits).';
|
||||
strDetail = 'Délai dépassé sur un lot identité — relancer Tout renvoyer (lots plus petits v4.72.972).';
|
||||
} else if (intHttp === 503 || intHttp === 502) {
|
||||
strDetail = 'HTTP ' + intHttp
|
||||
+ ' sur le serveur MS1 (timeout / surcharge) pendant un lot — '
|
||||
+ 'pas un refus métier CT. Relancer Tout renvoyer ; le modèle découpe maintenant identité + dossards.';
|
||||
} else if (intHttp === 0) {
|
||||
strDetail = 'Connexion coupée sur un lot (serveur surchargé / PHP arrêté). Relancer Tout renvoyer.';
|
||||
strDetail = 'Connexion coupée sur un lot. Relancer Tout renvoyer.';
|
||||
} else {
|
||||
strDetail = 'Erreur réseau sur un lot (' + strStatus + ', HTTP ' + intHttp + ').';
|
||||
}
|
||||
if (jqXHR && jqXHR.responseText) {
|
||||
var strBody = String(jqXHR.responseText).replace(/\s+/g, ' ').substr(0, 220);
|
||||
if (strBody) {
|
||||
var strBody = String(jqXHR.responseText).replace(/\s+/g, ' ').substr(0, 180);
|
||||
if (strBody && strBody.indexOf('{') !== 0) {
|
||||
strDetail += ' — ' + strBody;
|
||||
}
|
||||
}
|
||||
tabErrAll.push(strDetail);
|
||||
finishPush();
|
||||
finishPush('interrupted');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user