From 847f2f57a7c5a1320ad693e47ae2cbc0eebeab6e Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 22 Jul 2026 15:19:51 -0400 Subject: [PATCH] =?UTF-8?q?MSIN-4328=20=E2=80=94=20Introduce=20a=20new=20b?= =?UTF-8?q?utton=20for=20full=20resync=20in=20the=20ChronoTrack=20admin=20?= =?UTF-8?q?panel,=20allowing=20all=20eligible=20participants=20to=20be=20r?= =?UTF-8?q?esent=20regardless=20of=20last=20push=20date.=20Update=20JavaSc?= =?UTF-8?q?ript=20to=20handle=20the=20new=20button's=20functionality=20and?= =?UTF-8?q?=20enhance=20logging=20for=20successful=20entries.=20Adjust=20p?= =?UTF-8?q?ush=20preparation=20logic=20to=20accommodate=20full=20resync=20?= =?UTF-8?q?options.=20Increment=20version=20code.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/chronotrack_api/fx_chronotrack_admin.php | 68 ++++++++++++++------ php/chronotrack_api/fx_chronotrack_sync.php | 49 ++++++++++++-- superadm/ajax_chronotrack_api.php | 5 +- 3 files changed, 94 insertions(+), 28 deletions(-) diff --git a/php/chronotrack_api/fx_chronotrack_admin.php b/php/chronotrack_api/fx_chronotrack_admin.php index 2ddf90e..abf4467 100644 --- a/php/chronotrack_api/fx_chronotrack_admin.php +++ b/php/chronotrack_api/fx_chronotrack_admin.php @@ -327,6 +327,9 @@ function fxChronotrackApiAdminRenderPushPanel($intEveId, $blnClosed = false, $ar echo ' Actualiser '; echo ' '; + echo ' '; echo ' '; echo ''; echo ''; - echo '

Envoi manuel : nouveaux / modifiés depuis le dernier push. ' + echo '

Envoyer = seulement les nouveaux / modifiés depuis le dernier push. ' + . 'Tout renvoyer = tous les éligibles, dates ignorées. ' + . 'Chaque envoi est journalisé (Voir les logs). ' . 'Cet écran se rafraîchit doucement chaque minute (statut MS1 seulement).

'; echo '
'; echo '
'; @@ -1010,6 +1015,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed var $sum = $('#ct_api_push_summary'); var $status = $('#ct_api_push_status'); var $btn = $('#ct_api_btn_push'); + var $btnFull = $('#ct_api_btn_push_full'); objPushPreview = res; if (!$sum.length && !$status.length) { return; @@ -1024,6 +1030,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed } $('#ct_api_push_blocked_wrap').addClass('d-none').empty(); $btn.prop('disabled', true); + $btnFull.prop('disabled', true); return; } @@ -1087,7 +1094,8 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed $sum.html(html); renderBlockedPanel(res); - $btn.prop('disabled', intPending <= 0); + $btn.prop('disabled', intPending <= 0 || blnClosed || blnPushRunning); + $btnFull.prop('disabled', intEligible <= 0 || blnClosed || blnPushRunning); // MSIN-4328 — auto-réparation au load : last_push déjà posé mais 100 % des // éligibles « à pousser » (= faux différentiel, ex. par_maj bumpé par backfill entry_id). @@ -1341,12 +1349,13 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed } var tabLogs = res.logs || []; var html = '
'; - html += 'Logs sync (50 derniers)'; + html += 'Logs sync (200 derniers)'; html += ''; html += '
'; if (!tabLogs.length) { html += '

Aucun log pour cet événement.

'; } else { + html += '

Chaque participant envoyé = ligne push_entry (ok ou error) avec external_id / dossard / nom.

'; html += '
'; html += ''; html += ''; @@ -1369,7 +1378,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed function loadSyncLogs() { var $wrap = $('#ct_api_logs_wrap'); $wrap.removeClass('d-none').html(waitHtml('Chargement des logs…')); - $.post(strAjaxUrl, { a: 'sync_logs', eve_id: intEveId, limit: 50 }, function (res) { + $.post(strAjaxUrl, { a: 'sync_logs', eve_id: intEveId, limit: 200 }, function (res) { if (rejectIfSessionLost(res)) { return; } @@ -1492,17 +1501,23 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed }); }); - $('#ct_api_btn_push').on('click', function () { + // MSIN-4328 — envoi manuel (différentiel) ou resync complète (force_full) + function runManualPush(blnForceFull) { var intPending = 0; if (objPushPreview) { - if (typeof objPushPreview.pending_push !== 'undefined') { + if (blnForceFull) { + intPending = objPushPreview.transferable || 0; + } else if (typeof objPushPreview.pending_push !== 'undefined') { intPending = objPushPreview.pending_push; } else { intPending = objPushPreview.to_push || objPushPreview.transferable || 0; } } - // MSIN-4328 — push immédiat, sans confirm - var $btn = $(this); + if (blnForceFull) { + if (!window.confirm('Tout renvoyer vers ChronoTrack ?\n\nTous les participants éligibles seront renvoyés, sans tenir compte des dates du dernier envoi.')) { + return; + } + } var $res = $('#ct_api_push_result'); var intChunk = 100; var intOffset = 0; @@ -1514,13 +1529,14 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed var tabErrAll = []; var blnFatal = false; - $btn.prop('disabled', true); blnPushRunning = true; - $('#ct_api_btn_push_refresh, #ct_api_btn_ct_count, #ct_api_btn_logs').prop('disabled', true); + $('#ct_api_btn_push, #ct_api_btn_push_full, #ct_api_btn_push_refresh, #ct_api_btn_ct_count, #ct_api_btn_logs').prop('disabled', true); function renderProgress(intDone, intTot, strExtra) { var intPct = (intTot > 0) ? Math.min(100, Math.round((intDone * 100) / intTot)) : 0; - var strHtml = '
Envoi — ' + var strHtml = '
' + + (blnForceFull ? 'Resync complète' : 'Envoi') + + ' — ' + intDone + ' / ' + intTot + (strExtra ? (' · ' + escHtml(strExtra)) : '') + '
'; @@ -1540,10 +1556,12 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed function finishPush() { blnPushRunning = false; $('#ct_api_btn_push_refresh, #ct_api_btn_ct_count, #ct_api_btn_logs').prop('disabled', false); - $btn.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 strState = blnFatal ? 'error' : ((intErrSum === 0) ? 'ok' : 'partial'); renderPushResult({ state: strState, @@ -1551,6 +1569,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed errors: tabErrAll.slice(0, 20) }); loadPushPreview(true); + loadSyncLogs(); } function pushChunk() { @@ -1577,8 +1596,6 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed finishPush(); return; } - // MSIN-4328 — ne pas abandonner le job si un lot a des erreurs : - // continuer jusqu’à done (premier envoi = tous les lots). if (res.state !== 'ok' && res.state !== 'partial' && res.state !== 'error') { blnFatal = true; if (res.message) { @@ -1591,7 +1608,7 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed renderPushResult(res); $('#ct_api_btn_push_refresh, #ct_api_btn_ct_count, #ct_api_btn_logs').prop('disabled', false); blnPushRunning = false; - $btn.prop('disabled', false); + loadPushPreview(true); return; } if (res.total) { @@ -1645,7 +1662,11 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed type: 'POST', dataType: 'json', timeout: 180000, - data: { a: 'sync_push_prepare', eve_id: intEveId } + data: { + a: 'sync_push_prepare', + eve_id: intEveId, + force_full: blnForceFull ? 1 : 0 + } }).done(function (prep) { if (rejectIfSessionLost(prep)) { return; @@ -1653,15 +1674,15 @@ 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_ct_count, #ct_api_btn_logs').prop('disabled', false); - blnPushRunning = false; - $btn.prop('disabled', false); + blnPushRunning = false; + loadPushPreview(true); return; } intTotal = parseInt(prep.total, 10) || intTotal; if (prep.chunk_size) { intChunk = parseInt(prep.chunk_size, 10) || intChunk; } - renderProgress(0, intTotal, 'démarrage'); + renderProgress(0, intTotal, blnForceFull ? 'resync complète' : 'démarrage'); pushChunk(); }).fail(function (jqXHR, strStatus) { var strMsg = 'Erreur préparation push (' + strStatus @@ -1673,8 +1694,15 @@ function fxChronotrackApiAdminRenderPageScript($intEveId, $arrConfig, $blnClosed renderPushResult({ state: 'error', message: strMsg }); $('#ct_api_btn_push_refresh, #ct_api_btn_ct_count, #ct_api_btn_logs').prop('disabled', false); blnPushRunning = false; - $btn.prop('disabled', false); + loadPushPreview(true); }); + } + + $('#ct_api_btn_push').on('click', function () { + runManualPush(false); + }); + $('#ct_api_btn_push_full').on('click', function () { + runManualPush(true); }); } diff --git a/php/chronotrack_api/fx_chronotrack_sync.php b/php/chronotrack_api/fx_chronotrack_sync.php index 59ad249..0645b1d 100644 --- a/php/chronotrack_api/fx_chronotrack_sync.php +++ b/php/chronotrack_api/fx_chronotrack_sync.php @@ -995,6 +995,22 @@ function fxChronotrackApiSyncPostEntriesAdaptive($intCtEventId, array $tabEntrie // MSIN-4328 — récupérer entry_id CT pour lien admin if ($intEveId > 0 && count($tabMeta) > 0) { fxChronotrackApiSyncApplyEntryIdsFromApiJson($arrPost['json'] ?? null, $tabMeta); + // MSIN-4328 — log précis de chaque envoi réussi (contrôle / audit) + foreach ($tabMeta as $arrM) { + if (!is_array($arrM)) { + continue; + } + $strMsg = 'external_id=' . ($arrM['external_id'] ?? '') + . ' bib=' . ($arrM['bib'] ?? '') + . ' ' . trim((string)($arrM['name'] ?? '')); + fxChronotrackApiSyncLog( + $intEveId, + intval($arrM['par_id'] ?? 0), + 'push_entry', + 'ok', + trim($strMsg) + ); + } } return array('ok' => $intCount, 'err' => 0); } @@ -1689,9 +1705,12 @@ function fxChronotrackApiSyncPushJobClear($intEveId) { /** * Prépare un job de push (liste éligibles + entry_id CT). MSIN-4328. + * + * @param array $arrOptions force_full=true → tous les éligibles (ignore last_push_at) */ -function fxChronotrackApiSyncPushPrepare($intEveId) { +function fxChronotrackApiSyncPushPrepare($intEveId, $arrOptions = array()) { $intEveId = intval($intEveId); + $blnForceFull = !empty($arrOptions['force_full']); $arrConfig = fxChronotrackApiConfigGet($intEveId); if ($arrConfig === null) { return array('state' => 'error', 'message' => 'Événement non lié à ChronoTrack'); @@ -1708,18 +1727,29 @@ function fxChronotrackApiSyncPushPrepare($intEveId) { // MSIN-4328 — ne plus journaliser push_skip ici (spam à chaque prepare/auto). // Les exclus restent dans l’UI « Détails & exclus » (preview), jamais dans le lot poussé. - // MSIN-4328 — ne pousser que les éligibles modifiés depuis last_push_at + // MSIN-4328 — différentiel par défaut ; force_full = tout renvoyer $strLastPushAt = trim((string)($arrConfig['last_push_at'] ?? '')); - $tabPending = fxChronotrackApiSyncFilterChangedSincePush($arrClass['transferable'], $strLastPushAt); + if ($blnForceFull) { + $tabPending = $arrClass['transferable']; + } else { + $tabPending = fxChronotrackApiSyncFilterChangedSincePush($arrClass['transferable'], $strLastPushAt); + } // Plus de scan CT à la préparation : POST upsert (crée ou maj) sans entry_id. $tabBatch = array(); $tabMeta = array(); foreach ($tabPending as $arrItem) { - $tabBatch[] = fxChronotrackApiSyncPayloadForPost($arrItem['payload']); + $arrPayload = fxChronotrackApiSyncPayloadForPost($arrItem['payload']); + $tabBatch[] = $arrPayload; $tabMeta[] = array( 'par_id' => intval($arrItem['row']['par_id'] ?? 0), - 'external_id' => $arrItem['payload']['external_id'], + 'external_id' => $arrPayload['external_id'] ?? '', + 'bib' => isset($arrPayload['bib']) ? (string)$arrPayload['bib'] : '', + 'name' => trim( + trim((string)($arrPayload['first_name'] ?? '')) + . ' ' + . trim((string)($arrPayload['last_name'] ?? '')) + ), ); } @@ -1738,15 +1768,17 @@ function fxChronotrackApiSyncPushPrepare($intEveId) { 'transferable' => count($arrClass['transferable']), 'pending_push' => 0, 'total' => 0, + 'force_full' => $blnForceFull ? 1 : 0, ); } + $strMode = $blnForceFull ? 'FULL resync' : 'différentiel'; fxChronotrackApiSyncLog( $intEveId, 0, 'push_prepare', 'ok', - 'job prêt — pending=' . count($tabBatch) + $strMode . ' — job prêt — pending=' . count($tabBatch) . ' éligibles=' . count($arrClass['transferable']) . ' exclus=' . intval($arrClass['blocked_count']) ); @@ -1756,6 +1788,7 @@ function fxChronotrackApiSyncPushPrepare($intEveId) { 'batch' => $tabBatch, 'meta' => $tabMeta, 'blocked_count' => $arrClass['blocked_count'], + 'force_full' => $blnForceFull ? 1 : 0, 'ok_sum' => 0, 'err_sum' => 0, 'created_at' => time(), @@ -1769,13 +1802,15 @@ function fxChronotrackApiSyncPushPrepare($intEveId) { return array( 'state' => 'ok', - 'message' => count($tabBatch) . ' prêts à envoyer', + 'message' => count($tabBatch) . ' prêts à envoyer' + . ($blnForceFull ? ' (resync complète)' : ''), 'total' => count($tabBatch), 'pending_push' => count($tabBatch), 'transferable' => count($arrClass['transferable']), 'blocked_count' => $arrClass['blocked_count'], 'chunk_size' => MSIN_API_CHRONOTRACK_SYNC_CHUNK_SIZE, 'ct_event_id' => $intCtEventId, + 'force_full' => $blnForceFull ? 1 : 0, ); } diff --git a/superadm/ajax_chronotrack_api.php b/superadm/ajax_chronotrack_api.php index 1245a90..57b2464 100644 --- a/superadm/ajax_chronotrack_api.php +++ b/superadm/ajax_chronotrack_api.php @@ -186,7 +186,10 @@ switch ($strAction) { break; } set_time_limit(180); - $tabRetour = fxChronotrackApiSyncPushPrepare($intEveId); + $blnForceFull = (intval($_REQUEST['force_full'] ?? 0) === 1); + $tabRetour = fxChronotrackApiSyncPushPrepare($intEveId, array( + 'force_full' => $blnForceFull, + )); break; case 'sync_push':
QuandActionStatutpar_idMessage