From c8aa6394f55aef45498d8d7c616afb4c7d892a4e Mon Sep 17 00:00:00 2001 From: stephan Date: Wed, 15 Jul 2026 13:04:09 -0400 Subject: [PATCH] =?UTF-8?q?MSIN-4464=20=E2=80=94=20Enhance=20audit=20loggi?= =?UTF-8?q?ng=20functionality=20for=20participant=20management.=20Introduc?= =?UTF-8?q?e=20new=20functions=20to=20track=20last=20touch=20events=20and?= =?UTF-8?q?=20handle=20cancellation/restoration=20of=20participant=20regis?= =?UTF-8?q?trations.=20Update=20admin=20interface=20to=20display=20recent?= =?UTF-8?q?=20logs=20and=20improve=20oversight=20of=20audit=20activities,?= =?UTF-8?q?=20ensuring=20better=20traceability=20of=20changes=20made=20to?= =?UTF-8?q?=20participant=20records.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- php/inc_fx_fiche_audit.php | 133 ++++++++++++++++++++ php/inc_fx_modifierinscriptions.php | 24 ++++ php/inc_fx_panier.php | 39 +++++- php/inc_fx_promoteur.php | 39 +++++- superadm/fiche_audit.php | 5 +- superadm/php/inc_fx_fiche_audit_admin.php | 142 +++++++++++++++++++++- 6 files changed, 371 insertions(+), 11 deletions(-) diff --git a/php/inc_fx_fiche_audit.php b/php/inc_fx_fiche_audit.php index e4b4fba..fa76c4d 100644 --- a/php/inc_fx_fiche_audit.php +++ b/php/inc_fx_fiche_audit.php @@ -315,6 +315,139 @@ function fxFicheAuditRecordChange( } } +/** + * MSIN-4464 — Dernière intervention seule (édition fiche, etc.), sans old→new. + */ +function fxFicheAuditRecordLastTouchOnly($intParId, $strTriggerField, $strSource = '', $intEveId = 0) +{ + global $objDatabase; + + $intParId = intval($intParId); + $strTriggerField = preg_replace('/[^a-z0-9_]/i', '', (string)$strTriggerField); + if ($strTriggerField === '') { + $strTriggerField = 'fiche'; + } + if ($intParId <= 0 || !isset($objDatabase) || !fxFicheAuditTablesReady()) { + return; + } + + $arrSettings = fxFicheAuditGetSettings(); + if (intval($arrSettings['track_last_touch']) !== 1) { + return; + } + + if ($intEveId <= 0) { + $intEveId = intval($objDatabase->fxGetVar( + 'SELECT eve_id FROM resultats_participants WHERE par_id = ' . $intParId . ' LIMIT 1' + )); + } + + $arrActor = fxFicheAuditResolveActor(); + fxFicheAuditInsertLogRow( + $intParId, + $intEveId, + 'last_touch', + '', + $strTriggerField, + $arrActor, + $strSource + ); + fxFicheAuditTrimField($intParId, 'last_touch', $arrSettings); +} + +/** + * Après annulation / rétablissement sur une commande (tous les par_id du pec). + */ +function fxFicheAuditRecordCancelRestoreForPec($intPecId, $strOld, $strNew, $strSource, $intEveId = 0) +{ + global $objDatabase; + + $intPecId = intval($intPecId); + if ($intPecId <= 0 || !isset($objDatabase) || !fxFicheAuditTablesReady()) { + return; + } + + $sql = 'SELECT par_id, eve_id FROM resultats_participants WHERE pec_id = ' . $intPecId; + $arrRows = $objDatabase->fxGetResults($sql); + if (!is_array($arrRows)) { + return; + } + + foreach ($arrRows as $row) { + if (!is_array($row) || empty($row['par_id'])) { + continue; + } + $intEveRow = intval($row['eve_id'] ?? 0); + if ($intEveRow <= 0) { + $intEveRow = intval($intEveId); + } + fxFicheAuditRecordChange( + intval($row['par_id']), + 'is_cancelled', + $strOld, + $strNew, + $strSource, + $intEveRow + ); + } +} + +/** + * Événements pour le sélecteur admin (actifs récents + ceux déjà dans les logs). + * + * @return array[] + */ +function fxFicheAuditAdminListEvents() +{ + global $objDatabase; + + if (!isset($objDatabase) || !fxFicheAuditTablesReady()) { + return array(); + } + + $sql = 'SELECT e.eve_id, e.eve_nom_fr, e.eve_date_debut,' + . ' (SELECT COUNT(*) FROM inscriptions_fiche_audit_log l WHERE l.eve_id = e.eve_id) AS fal_nb' + . ' FROM inscriptions_evenements e' + . ' WHERE e.eve_actif = 1' + . ' OR EXISTS (SELECT 1 FROM inscriptions_fiche_audit_log l2 WHERE l2.eve_id = e.eve_id)' + . ' ORDER BY e.eve_date_debut DESC, e.eve_id DESC' + . ' LIMIT 300'; + $arrRows = $objDatabase->fxGetResults($sql); + + return is_array($arrRows) ? $arrRows : array(); +} + +/** + * Derniers logs d'un événement (contrôle admin). + * + * @return array[] + */ +function fxFicheAuditAdminListRecentByEve($intEveId, $intLimit = 100) +{ + global $objDatabase; + + $intEveId = intval($intEveId); + $intLimit = intval($intLimit); + if ($intLimit !== 200) { + $intLimit = 100; + } + if ($intEveId <= 0 || !isset($objDatabase) || !fxFicheAuditTablesReady()) { + return array(); + } + + $sql = 'SELECT l.fal_id, l.par_id, l.eve_id, l.field_key, l.old_value, l.new_value,' + . ' l.changed_by_com_id, l.changed_by_label, l.source, l.created_at,' + . ' p.par_prenom, p.par_nom, p.no_bib' + . ' FROM inscriptions_fiche_audit_log l' + . ' LEFT JOIN resultats_participants p ON p.par_id = l.par_id' + . ' WHERE l.eve_id = ' . $intEveId + . ' ORDER BY l.created_at DESC, l.fal_id DESC' + . ' LIMIT ' . $intLimit; + $arrRows = $objDatabase->fxGetResults($sql); + + return is_array($arrRows) ? $arrRows : array(); +} + /** * @param array{com_id:int,label:string} $arrActor */ diff --git a/php/inc_fx_modifierinscriptions.php b/php/inc_fx_modifierinscriptions.php index 29d3fc8..c5a6bf1 100644 --- a/php/inc_fx_modifierinscriptions.php +++ b/php/inc_fx_modifierinscriptions.php @@ -310,6 +310,18 @@ function fxAnnulerRetablirInscription($tabEpreuve, $int_pec_id) { if (!$qryInscription || !$qryParticipants) { $tabRetour['state'] = 'error'; fxcreer_log("Erreur d'annulation d'inscription : " . $_SESSION['com_info']['com_courriel']); + } else { + // MSIN-4464 — audit annulation + if (!function_exists('fxFicheAuditRecordCancelRestoreForPec')) { + require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php'; + } + fxFicheAuditRecordCancelRestoreForPec( + intval($int_pec_id), + '0', + '1', + 'annuler', + intval($tabEpreuve['eve_id'] ?? 0) + ); } } else { $tabRetour['action'] = 'retablir'; @@ -327,6 +339,18 @@ function fxAnnulerRetablirInscription($tabEpreuve, $int_pec_id) { if (!$qryInscription || !$qryParticipants) { $tabRetour['state'] = 'error'; fxcreer_log("Erreur d'annulation d'inscription : " . $_SESSION['com_info']['com_courriel']); + } else { + // MSIN-4464 — audit rétablissement + if (!function_exists('fxFicheAuditRecordCancelRestoreForPec')) { + require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php'; + } + fxFicheAuditRecordCancelRestoreForPec( + intval($int_pec_id), + '1', + '0', + 'retablir', + intval($tabEpreuve['eve_id'] ?? 0) + ); } // ajuster qte epreuve diff --git a/php/inc_fx_panier.php b/php/inc_fx_panier.php index 5baa6a3..f5f592d 100644 --- a/php/inc_fx_panier.php +++ b/php/inc_fx_panier.php @@ -797,11 +797,26 @@ LEFT JOIN inscriptions_panier_epreuves ee on e.epr_id=ee.epr_id $intDiff = ($tabAncienneEpreuve['epr_nb_participants'] - $tabNouvelleEpreuve['epr_nb_participants']); for ($i = count($tabInscription); $i > $tabNouvelleEpreuve['epr_nb_participants']; $i--) { - $sqlUpdate = "UPDATE resultats_participants SET is_cancelled = 1, par_modification_promoteur = 1 WHERE par_id = " . intval($tabInscription[$i]['par_id']); + $intParCancel = intval($tabInscription[$i]['par_id']); + $sqlUpdate = "UPDATE resultats_participants SET is_cancelled = 1, par_modification_promoteur = 1 WHERE par_id = " . $intParCancel; $qryUpdate = $objDatabase->fxQuery($sqlUpdate); - if (!$qryUpdate) + if (!$qryUpdate) { fxcreer_log("Erreur lors du changemenet d'épreuve par le promoteur."); + } else { + // MSIN-4464 — participants surplus annulés au changement d'épreuve + if (!function_exists('fxFicheAuditRecordChange')) { + require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php'; + } + fxFicheAuditRecordChange( + $intParCancel, + 'is_cancelled', + '0', + '1', + 'changement_epreuve', + intval($tabInscription[$i]['eve_id'] ?? ($tabEvenement['general']['eve_id'] ?? 0)) + ); + } } $sqlUpdate = "UPDATE resultats_epreuves_commandees SET pec_equipe = " . $tabNouvelleEpreuve['epr_equipe'] . " WHERE pec_id_original = " . $intpec_id; @@ -921,6 +936,17 @@ LEFT JOIN inscriptions_panier_epreuves ee on e.epr_id=ee.epr_id if (!$qryUpdate) { fxcreer_log('Erreur dans la mise à jour des participants.(' . $tabInscription[1]['par_courriel'] . ')'); + } elseif ($strTableSqlPart === 'resultats_participants' && $strModMarker !== '') { + // MSIN-4464 — édition fiche promoteur : dernière intervention (pas le détail de chaque champ). + if (!function_exists('fxFicheAuditRecordLastTouchOnly')) { + require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php'; + } + fxFicheAuditRecordLastTouchOnly( + intval($intParId), + 'fiche', + 'fiche_edit', + intval($tabEvenement['general']['eve_id'] ?? 0) + ); } // catégorie if ($tabEvenement['general']['eve_categorie_auto'] == 1) { @@ -4094,8 +4120,15 @@ function fxMajEpreuvesCommandees($str_ancien_no_panier, $pec_id_old, $pec_id_new $sqlUpdate = "UPDATE resultats_participants SET is_cancelled = 1, par_modification_promoteur = 1, par_maj = '" . fxGetDateTime() . "' WHERE pec_id = " . intval($pec_id_old); $qryUpdateParticipants = $objDatabase->fxQuery($sqlUpdate); - if (!$qryUpdateEpreuve || !$qryUpdateParticipants) + if (!$qryUpdateEpreuve || !$qryUpdateParticipants) { fxcreer_log("Erreur de désactivation de l'ancienne épreuve lors d'un upgrade ou transfert."); + } else { + // MSIN-4464 — annulation suite upgrade / transfert + if (!function_exists('fxFicheAuditRecordCancelRestoreForPec')) { + require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php'; + } + fxFicheAuditRecordCancelRestoreForPec(intval($pec_id_old), '0', '1', 'transfert_upgrade', 0); + } } unset($_SESSION['ancien_no_panier']); diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index 736db25..41d4aa3 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -1789,6 +1789,11 @@ function fxShowBibNumbersTable($tab_epreuve, $strLangue, $intEveId = 0) { function fxSetBibNumbers($tab_data, $int_epr_id, $int_start, $int_finish, $int_type, $int_par_equipe, $str_action = '', $int_mode,$int_finish_dispo=999999) { global $strLangue, $objDatabase, $vDomaine; + // MSIN-4464 — audit dossards batch + if (!function_exists('fxFicheAuditRecordChange')) { + require_once dirname(__FILE__) . '/inc_fx_fiche_audit.php'; + } + if ($strLangue == 'fr') { $strPage = '/compte'; } else { @@ -1902,6 +1907,9 @@ function fxSetBibNumbers($tab_data, $int_epr_id, $int_start, $int_finish, $int_t for ($i = 1; $i <= count($tabInscriptions); $i++) { if ($tabInscriptions[$i]['rol_id'] != 3 && $tabInscriptions[$i]['rol_id'] != 4) { + $intParBatch = intval($tabInscriptions[$i]['par_id']); + $strOldBibBatch = (string)($tabInscriptions[$i]['no_bib'] ?? ''); + $strNewBibBatch = ($str_action == 'reset') ? '' : (string)($intNoBib + $intCtr); $sqlUpdate = "UPDATE resultats_participants "; if ($str_action == 'reset') { $sqlUpdate .= "SET no_bib = '', par_date_bib = null"; @@ -1919,8 +1927,18 @@ function fxSetBibNumbers($tab_data, $int_epr_id, $int_start, $int_finish, $int_t $sqlUpdate .= " WHERE par_id = " . $tabInscriptions[$i]['par_id'] . " AND is_cancelled = 0"; $qryUpdate = $objDatabase->fxQuery($sqlUpdate); - if (!$qryUpdate) + if (!$qryUpdate) { fxcreer_log('Erreur lors de mise a jour du numero de dossard du participant ' . $tabInscriptions[$i]['par_id']); + } else { + fxFicheAuditRecordChange( + $intParBatch, + 'no_bib', + $strOldBibBatch, + $strNewBibBatch, + ($str_action == 'reset') ? 'bib_batch_reset' : 'bib_batch', + intval($tabInscriptions[$i]['eve_id'] ?? 0) + ); + } $sqlUpdate = "UPDATE resultats_epreuves_commandees SET no_equipe = '" . $objDatabase->fxEscape($intNoBib + $intCtr) . "' WHERE epr_id = " . intval($tabInscriptions[$i]['epr_id']) . " AND pec_id_original = " . $tabInscriptions[$i]['pec_id']; $qryUpdate = $objDatabase->fxQuery($sqlUpdate); @@ -1967,16 +1985,29 @@ function fxSetBibNumbers($tab_data, $int_epr_id, $int_start, $int_finish, $int_t } else{ $intNoBib = $strNumeroEquipe; } + $intParEqBatch = intval($tabEquipe[$j]['par_id']); + $strOldBibEq = (string)($tabEquipe[$j]['no_bib'] ?? ''); + $strNewBibEq = (string)$intNoBib; if($tabInscriptions[$i]['par_date_bib'] == null){ - $sqlUpdatePart = "UPDATE resultats_participants SET par_date_bib = '" . fxGetDateTime() . "', no_bib = '" . $objDatabase->fxEscape($intNoBib). "' WHERE par_id = " . intval($tabEquipe[$j]['par_id']); + $sqlUpdatePart = "UPDATE resultats_participants SET par_date_bib = '" . fxGetDateTime() . "', no_bib = '" . $objDatabase->fxEscape($intNoBib). "' WHERE par_id = " . $intParEqBatch; } else{ - $sqlUpdatePart = "UPDATE resultats_participants SET no_bib = '" . $objDatabase->fxEscape($intNoBib) . "' WHERE par_id = " . intval($tabEquipe[$j]['par_id']); + $sqlUpdatePart = "UPDATE resultats_participants SET no_bib = '" . $objDatabase->fxEscape($intNoBib) . "' WHERE par_id = " . $intParEqBatch; // $sqlUpdatePart = "UPDATE resultats_participants SET par_maj = '" . fxGetDateTime() . "', no_bib = '" . $objDatabase->fxEscape($intNoBib) . "' WHERE par_id = " . intval($tabEquipe[$j]['par_id']); // $sqlUpdatePart = "UPDATE resultats_participants SET par_maj = '" . fxGetDateTime() . "', par_modification_promoteur = 1, no_bib = '" . $objDatabase->fxEscape($intNoBib) . "' WHERE par_id = " . intval($tabEquipe[$j]['par_id']); } $qryUpdatePart = $objDatabase->fxQuery($sqlUpdatePart); - if (!$qryUpdatePart) + if (!$qryUpdatePart) { fxcreer_log('Erreur lors de mise a jour du numero de dossard du participant ' . $tabInscriptions[$i]['par_id']); + } else { + fxFicheAuditRecordChange( + $intParEqBatch, + 'no_bib', + $strOldBibEq, + $strNewBibEq, + 'bib_batch', + intval($tabEquipe[$j]['eve_id'] ?? 0) + ); + } } } $intCtr++; diff --git a/superadm/fiche_audit.php b/superadm/fiche_audit.php index b0acec1..0e2b9c3 100644 --- a/superadm/fiche_audit.php +++ b/superadm/fiche_audit.php @@ -47,7 +47,10 @@ include('inc_header.php');
'superadm-content-v2-zone superadm-content-v2-zone--flush', )); fxFicheAuditAdminRenderPage($strFlash, $strError); diff --git a/superadm/php/inc_fx_fiche_audit_admin.php b/superadm/php/inc_fx_fiche_audit_admin.php index 53dfdb4..28a3afb 100644 --- a/superadm/php/inc_fx_fiche_audit_admin.php +++ b/superadm/php/inc_fx_fiche_audit_admin.php @@ -16,6 +16,11 @@ function fxFicheAuditAdminRenderPage($strFlash = '', $strError = '') $arrSettings = fxFicheAuditGetSettings(); $arrFields = fxFicheAuditGetFieldsForAdmin(); $strDisabled = ($blnCanEdit && $blnTablesOk) ? '' : ' disabled'; + $intEveLogs = isset($_GET['eve_id']) ? intval($_GET['eve_id']) : 0; + $intLimitLogs = (isset($_GET['limit']) && intval($_GET['limit']) === 200) ? 200 : 100; + $blnShowLogs = ($blnTablesOk && $intEveLogs > 0 && isset($_GET['voir_logs'])); + $arrEvents = $blnTablesOk ? fxFicheAuditAdminListEvents() : array(); + $arrLogs = $blnShowLogs ? fxFicheAuditAdminListRecentByEve($intEveLogs, $intLimitLogs) : array(); if ($strFlash !== '') { echo '
' . htmlspecialchars($strFlash, ENT_QUOTES, 'UTF-8') . '
'; @@ -35,7 +40,7 @@ function fxFicheAuditAdminRenderPage($strFlash = '', $strError = '')
- Lecture seule : vous pouvez consulter la configuration. Modification réservée. + Lecture seule : vous pouvez consulter la configuration et les logs. Modification réservée.
@@ -50,8 +55,9 @@ function fxFicheAuditAdminRenderPage($strFlash = '', $strError = '')
  • Rétention — on ne garde pas un log sans fin : durée max + nombre max d'entrées par champ × participant.
  • - L'enregistrement est actif pour statut, dossard et dossard remis (selon whitelist / dernière intervention). - L'affichage « ? » sur la fiche et le droit promoteur associé viennent dans une étape suivante. + Points branchés : statut, dossard manuel, dossard remis, annulation/rétablissement, édition fiche, + batch dossards, surplus changement d'épreuve, transfert/upgrade. + L'affichage « ? » sur la fiche et le droit promoteur associé viennent ensuite.

    @@ -136,5 +142,135 @@ function fxFicheAuditAdminRenderPage($strFlash = '', $strError = '') + +
    + +
    +
    + Contrôle des logs (admin) +
    +
    +

    + Affiche les dernières entrées enregistrées pour un événement — utile pour valider que ça logue bien. +

    + +

    Tables absentes — impossible d'afficher les logs.

    + +
    + +
    + + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + Logs — événement # + ( / max) + + Fermer +
    +
    + +

    Aucun log pour cet événement.

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    QuandParticipantChampAvantAprèsParSource
    +
    + +
    +