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':