MSIN-4464 — Add configuration for fiche audit traceability in admin interface. Introduce a new sidebar section for audited fields and retention, enhancing the user experience by providing easy access to audit-related features.
This commit is contained in:
260
php/inc_fx_fiche_audit.php
Normal file
260
php/inc_fx_fiche_audit.php
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MSIN-4464 — Traçabilité fiches (config whitelist + rétention).
|
||||||
|
* Writers / UI fiche (« ? ») : phase suivante.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Même allowlist que les kits ($arrEveAccesKitEditors dans inc_settings.php).
|
||||||
|
*/
|
||||||
|
if (!function_exists('fxAdminCanEditFicheAudit')) {
|
||||||
|
function fxAdminCanEditFicheAudit()
|
||||||
|
{
|
||||||
|
return function_exists('fxAdminCanEditEveAccesKits') && fxAdminCanEditEveAccesKits();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tables MSIN-4464 présentes ? */
|
||||||
|
function fxFicheAuditTablesReady()
|
||||||
|
{
|
||||||
|
global $objDatabase;
|
||||||
|
static $blnReady = null;
|
||||||
|
|
||||||
|
if ($blnReady !== null) {
|
||||||
|
return $blnReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($objDatabase)) {
|
||||||
|
$blnReady = false;
|
||||||
|
return $blnReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tab = $objDatabase->fxGetResults("SHOW TABLES LIKE 'inscriptions_fiche_audit_settings'");
|
||||||
|
$blnReady = ($tab != null && count($tab) > 0);
|
||||||
|
|
||||||
|
return $blnReady;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catalogue connu (ordre d'affichage admin). Les clés absentes en BD sont
|
||||||
|
* proposées à l'écran et créées au premier enregistrement.
|
||||||
|
*
|
||||||
|
* @return array[]
|
||||||
|
*/
|
||||||
|
function fxFicheAuditFieldCatalog()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array(
|
||||||
|
'field_key' => 'par_statut_course',
|
||||||
|
'field_label_fr' => 'Statut de course',
|
||||||
|
'field_label_en' => 'Race status',
|
||||||
|
'field_tri' => 10,
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'field_key' => 'no_bib',
|
||||||
|
'field_label_fr' => 'Numéro de dossard',
|
||||||
|
'field_label_en' => 'Bib number',
|
||||||
|
'field_tri' => 20,
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'field_key' => 'no_bib_remis',
|
||||||
|
'field_label_fr' => 'Dossard remis',
|
||||||
|
'field_label_en' => 'Bib handed out',
|
||||||
|
'field_tri' => 30,
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'field_key' => 'is_cancelled',
|
||||||
|
'field_label_fr' => 'Annulation',
|
||||||
|
'field_label_en' => 'Cancellation',
|
||||||
|
'field_tri' => 40,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{retention_days:int,retention_max_per_field:int,track_last_touch:int}
|
||||||
|
*/
|
||||||
|
function fxFicheAuditGetSettings()
|
||||||
|
{
|
||||||
|
global $objDatabase;
|
||||||
|
|
||||||
|
$arrDefault = array(
|
||||||
|
'retention_days' => 90,
|
||||||
|
'retention_max_per_field' => 10,
|
||||||
|
'track_last_touch' => 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isset($objDatabase) || !fxFicheAuditTablesReady()) {
|
||||||
|
return $arrDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = 'SELECT retention_days, retention_max_per_field, track_last_touch'
|
||||||
|
. ' FROM inscriptions_fiche_audit_settings WHERE setting_id = 1 LIMIT 1';
|
||||||
|
$row = $objDatabase->fxGetRow($sql);
|
||||||
|
if ($row === null || !is_array($row)) {
|
||||||
|
return $arrDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'retention_days' => max(1, intval($row['retention_days'])),
|
||||||
|
'retention_max_per_field' => max(1, intval($row['retention_max_per_field'])),
|
||||||
|
'track_last_touch' => (intval($row['track_last_touch']) === 1) ? 1 : 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Champs whitelist fusionnés avec le catalogue (actif = 0 si absent en BD).
|
||||||
|
*
|
||||||
|
* @return array[]
|
||||||
|
*/
|
||||||
|
function fxFicheAuditGetFieldsForAdmin()
|
||||||
|
{
|
||||||
|
global $objDatabase;
|
||||||
|
|
||||||
|
$arrByKey = array();
|
||||||
|
if (isset($objDatabase) && fxFicheAuditTablesReady()) {
|
||||||
|
$sql = 'SELECT field_key, field_label_fr, field_label_en, field_actif, field_tri'
|
||||||
|
. ' FROM inscriptions_fiche_audit_fields ORDER BY field_tri, field_key';
|
||||||
|
$arrRows = $objDatabase->fxGetResults($sql);
|
||||||
|
if (is_array($arrRows)) {
|
||||||
|
foreach ($arrRows as $row) {
|
||||||
|
if (!is_array($row) || empty($row['field_key'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$arrByKey[$row['field_key']] = array(
|
||||||
|
'field_key' => (string)$row['field_key'],
|
||||||
|
'field_label_fr' => (string)$row['field_label_fr'],
|
||||||
|
'field_label_en' => (string)$row['field_label_en'],
|
||||||
|
'field_actif' => (intval($row['field_actif']) === 1) ? 1 : 0,
|
||||||
|
'field_tri' => intval($row['field_tri']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$arrOut = array();
|
||||||
|
foreach (fxFicheAuditFieldCatalog() as $arrCat) {
|
||||||
|
$strKey = $arrCat['field_key'];
|
||||||
|
if (isset($arrByKey[$strKey])) {
|
||||||
|
$arrOut[] = $arrByKey[$strKey];
|
||||||
|
unset($arrByKey[$strKey]);
|
||||||
|
} else {
|
||||||
|
$arrOut[] = array(
|
||||||
|
'field_key' => $strKey,
|
||||||
|
'field_label_fr' => $arrCat['field_label_fr'],
|
||||||
|
'field_label_en' => $arrCat['field_label_en'],
|
||||||
|
'field_actif' => 0,
|
||||||
|
'field_tri' => intval($arrCat['field_tri']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Champs éventuels hors catalogue (déjà en BD)
|
||||||
|
foreach ($arrByKey as $row) {
|
||||||
|
$arrOut[] = $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $arrOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clés actives (pour usage futur des writers).
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
function fxFicheAuditGetActiveFieldKeys()
|
||||||
|
{
|
||||||
|
global $objDatabase;
|
||||||
|
|
||||||
|
if (!isset($objDatabase)) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = 'SELECT field_key FROM inscriptions_fiche_audit_fields'
|
||||||
|
. ' WHERE field_actif = 1 ORDER BY field_tri, field_key';
|
||||||
|
$arrRows = $objDatabase->fxGetResults($sql);
|
||||||
|
$arrKeys = array();
|
||||||
|
if (is_array($arrRows)) {
|
||||||
|
foreach ($arrRows as $row) {
|
||||||
|
if (!empty($row['field_key'])) {
|
||||||
|
$arrKeys[] = (string)$row['field_key'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $arrKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $arrPost
|
||||||
|
* @return array{state:string,message:string}
|
||||||
|
*/
|
||||||
|
function fxFicheAuditSaveConfig($arrPost)
|
||||||
|
{
|
||||||
|
global $objDatabase;
|
||||||
|
|
||||||
|
if (!isset($objDatabase)) {
|
||||||
|
return array('state' => 'error', 'message' => 'Base de données indisponible.');
|
||||||
|
}
|
||||||
|
if (!fxFicheAuditTablesReady()) {
|
||||||
|
return array(
|
||||||
|
'state' => 'error',
|
||||||
|
'message' => 'Tables absentes : exécuter sql/MSIN-4464-fiche-audit-config.sql',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$intDays = isset($arrPost['retention_days']) ? intval($arrPost['retention_days']) : 90;
|
||||||
|
$intMax = isset($arrPost['retention_max_per_field']) ? intval($arrPost['retention_max_per_field']) : 10;
|
||||||
|
$intLastTouch = !empty($arrPost['track_last_touch']) ? 1 : 0;
|
||||||
|
|
||||||
|
if ($intDays < 1 || $intDays > 3650) {
|
||||||
|
return array('state' => 'error', 'message' => 'Rétention (jours) : entre 1 et 3650.');
|
||||||
|
}
|
||||||
|
if ($intMax < 1 || $intMax > 100) {
|
||||||
|
return array('state' => 'error', 'message' => 'Max par champ : entre 1 et 100.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$arrActif = array();
|
||||||
|
if (!empty($arrPost['field_actif']) && is_array($arrPost['field_actif'])) {
|
||||||
|
foreach ($arrPost['field_actif'] as $strKey) {
|
||||||
|
$strKey = preg_replace('/[^a-z0-9_]/i', '', (string)$strKey);
|
||||||
|
if ($strKey !== '') {
|
||||||
|
$arrActif[$strKey] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sqlSettings = 'INSERT INTO inscriptions_fiche_audit_settings'
|
||||||
|
. ' (setting_id, retention_days, retention_max_per_field, track_last_touch)'
|
||||||
|
. ' VALUES (1, ' . $intDays . ', ' . $intMax . ', ' . $intLastTouch . ')'
|
||||||
|
. ' ON DUPLICATE KEY UPDATE'
|
||||||
|
. ' retention_days = VALUES(retention_days),'
|
||||||
|
. ' retention_max_per_field = VALUES(retention_max_per_field),'
|
||||||
|
. ' track_last_touch = VALUES(track_last_touch)';
|
||||||
|
if (!$objDatabase->fxQuery($sqlSettings)) {
|
||||||
|
return array('state' => 'error', 'message' => 'Erreur à l\'enregistrement des réglages.');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (fxFicheAuditGetFieldsForAdmin() as $arrField) {
|
||||||
|
$strKey = $arrField['field_key'];
|
||||||
|
$intActif = isset($arrActif[$strKey]) ? 1 : 0;
|
||||||
|
$strLabelFr = $objDatabase->fxEscape($arrField['field_label_fr']);
|
||||||
|
$strLabelEn = $objDatabase->fxEscape($arrField['field_label_en']);
|
||||||
|
$intTri = intval($arrField['field_tri']);
|
||||||
|
|
||||||
|
$sqlField = 'INSERT INTO inscriptions_fiche_audit_fields'
|
||||||
|
. ' (field_key, field_label_fr, field_label_en, field_actif, field_tri)'
|
||||||
|
. " VALUES ('" . $objDatabase->fxEscape($strKey) . "', '" . $strLabelFr . "', '"
|
||||||
|
. $strLabelEn . "', " . $intActif . ', ' . $intTri . ')'
|
||||||
|
. ' ON DUPLICATE KEY UPDATE'
|
||||||
|
. ' field_label_fr = VALUES(field_label_fr),'
|
||||||
|
. ' field_label_en = VALUES(field_label_en),'
|
||||||
|
. ' field_actif = VALUES(field_actif),'
|
||||||
|
. ' field_tri = VALUES(field_tri)';
|
||||||
|
if (!$objDatabase->fxQuery($sqlField)) {
|
||||||
|
return array('state' => 'error', 'message' => 'Erreur à l\'enregistrement du champ ' . $strKey . '.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array('state' => 'ok', 'message' => 'Configuration enregistrée.');
|
||||||
|
}
|
||||||
@ -27,6 +27,7 @@ define('KEYCHAIN_HTTP_EDIT_SHIFT', 100);
|
|||||||
define('KEYCHAIN_HTTP_EDIT_ALLOWED_COM_IDS', '');
|
define('KEYCHAIN_HTTP_EDIT_ALLOWED_COM_IDS', '');
|
||||||
|
|
||||||
// MSIN-4401 — Qui peut créer / modifier / supprimer les définitions de kits (pas l'assignation).
|
// MSIN-4401 — Qui peut créer / modifier / supprimer les définitions de kits (pas l'assignation).
|
||||||
|
// MSIN-4464 — Même liste pour la config traçabilité fiche (whitelist + rétention).
|
||||||
// Valeurs = com_courriel ou com_login (session superadm usa_info).
|
// Valeurs = com_courriel ou com_login (session superadm usa_info).
|
||||||
$arrEveAccesKitEditors = array(
|
$arrEveAccesKitEditors = array(
|
||||||
'stephan@progiweb.ca',
|
'stephan@progiweb.ca',
|
||||||
|
|||||||
80
sql/MSIN-4464-fiche-audit-config.sql
Normal file
80
sql/MSIN-4464-fiche-audit-config.sql
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
-- MSIN-4464 — Traçabilité fiches : config whitelist + rétention
|
||||||
|
-- Prérequis : aucun
|
||||||
|
-- Notes prod : idempotent (CREATE IF NOT EXISTS + seeds via NOT EXISTS)
|
||||||
|
|
||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
/* Réglages globaux (1 seule ligne) */
|
||||||
|
CREATE TABLE IF NOT EXISTS `inscriptions_fiche_audit_settings` (
|
||||||
|
`setting_id` tinyint(3) unsigned NOT NULL DEFAULT 1,
|
||||||
|
`retention_days` smallint(5) unsigned NOT NULL DEFAULT 90
|
||||||
|
COMMENT 'MSIN-4464 — durée de conservation des entrées détaillées',
|
||||||
|
`retention_max_per_field` smallint(5) unsigned NOT NULL DEFAULT 10
|
||||||
|
COMMENT 'MSIN-4464 — max d''entrées gardées par champ × participant',
|
||||||
|
`track_last_touch` tinyint(1) unsigned NOT NULL DEFAULT 1
|
||||||
|
COMMENT 'MSIN-4464 — noter aussi qui a touché la fiche (sans détail de champ)',
|
||||||
|
`setting_updated_at` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`setting_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='MSIN-4464 — réglages traçabilité fiche';
|
||||||
|
|
||||||
|
INSERT INTO `inscriptions_fiche_audit_settings`
|
||||||
|
(`setting_id`, `retention_days`, `retention_max_per_field`, `track_last_touch`)
|
||||||
|
SELECT 1, 90, 10, 1
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM `inscriptions_fiche_audit_settings` WHERE `setting_id` = 1
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Whitelist des champs à tracer en détail (old → new + auteur) */
|
||||||
|
CREATE TABLE IF NOT EXISTS `inscriptions_fiche_audit_fields` (
|
||||||
|
`field_key` varchar(64) NOT NULL,
|
||||||
|
`field_label_fr` varchar(100) NOT NULL,
|
||||||
|
`field_label_en` varchar(100) NOT NULL,
|
||||||
|
`field_actif` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
|
`field_tri` smallint(5) unsigned NOT NULL DEFAULT 0,
|
||||||
|
`field_updated_at` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`field_key`),
|
||||||
|
KEY `idx_faf_actif_tri` (`field_actif`, `field_tri`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='MSIN-4464 — whitelist champs audités fiche';
|
||||||
|
|
||||||
|
INSERT INTO `inscriptions_fiche_audit_fields`
|
||||||
|
(`field_key`, `field_label_fr`, `field_label_en`, `field_actif`, `field_tri`)
|
||||||
|
SELECT 'par_statut_course', 'Statut de course', 'Race status', 1, 10
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM `inscriptions_fiche_audit_fields` WHERE `field_key` = 'par_statut_course');
|
||||||
|
|
||||||
|
INSERT INTO `inscriptions_fiche_audit_fields`
|
||||||
|
(`field_key`, `field_label_fr`, `field_label_en`, `field_actif`, `field_tri`)
|
||||||
|
SELECT 'no_bib', 'Numéro de dossard', 'Bib number', 1, 20
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM `inscriptions_fiche_audit_fields` WHERE `field_key` = 'no_bib');
|
||||||
|
|
||||||
|
INSERT INTO `inscriptions_fiche_audit_fields`
|
||||||
|
(`field_key`, `field_label_fr`, `field_label_en`, `field_actif`, `field_tri`)
|
||||||
|
SELECT 'no_bib_remis', 'Dossard remis', 'Bib handed out', 0, 30
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM `inscriptions_fiche_audit_fields` WHERE `field_key` = 'no_bib_remis');
|
||||||
|
|
||||||
|
INSERT INTO `inscriptions_fiche_audit_fields`
|
||||||
|
(`field_key`, `field_label_fr`, `field_label_en`, `field_actif`, `field_tri`)
|
||||||
|
SELECT 'is_cancelled', 'Annulation', 'Cancellation', 0, 40
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM `inscriptions_fiche_audit_fields` WHERE `field_key` = 'is_cancelled');
|
||||||
|
|
||||||
|
/* Journal détaillé — writers + UI « ? » dans une phase suivante */
|
||||||
|
CREATE TABLE IF NOT EXISTS `inscriptions_fiche_audit_log` (
|
||||||
|
`fal_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`par_id` int(10) unsigned NOT NULL,
|
||||||
|
`eve_id` int(10) unsigned DEFAULT NULL,
|
||||||
|
`field_key` varchar(64) NOT NULL
|
||||||
|
COMMENT 'champ whitelisté, ou last_touch pour intervention globale',
|
||||||
|
`old_value` varchar(255) DEFAULT NULL,
|
||||||
|
`new_value` varchar(255) DEFAULT NULL,
|
||||||
|
`changed_by_com_id` int(10) unsigned DEFAULT NULL,
|
||||||
|
`changed_by_label` varchar(120) DEFAULT NULL,
|
||||||
|
`source` varchar(32) DEFAULT NULL
|
||||||
|
COMMENT 'fiche, liste, mobile, system…',
|
||||||
|
`created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`fal_id`),
|
||||||
|
KEY `idx_fal_par_field_at` (`par_id`, `field_key`, `created_at`),
|
||||||
|
KEY `idx_fal_eve_at` (`eve_id`, `created_at`),
|
||||||
|
KEY `idx_fal_created` (`created_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
|
||||||
|
COMMENT='MSIN-4464 — historique changements fiche (détail + last_touch)';
|
||||||
61
superadm/fiche_audit.php
Normal file
61
superadm/fiche_audit.php
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MSIN-4464 — Traçabilité fiche : whitelist + rétention (super admin).
|
||||||
|
*/
|
||||||
|
|
||||||
|
$strLangue = 'fr';
|
||||||
|
$strPage = 'fiche_audit.php';
|
||||||
|
|
||||||
|
require_once('php/inc_start_time.php');
|
||||||
|
require_once('php/inc_functions.php');
|
||||||
|
require_once('php/inc_fx_fiche_audit_admin.php');
|
||||||
|
|
||||||
|
global $objDatabase;
|
||||||
|
|
||||||
|
if (empty($_SESSION['usa_id'])) {
|
||||||
|
header('Location: login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$strFlash = '';
|
||||||
|
$strError = '';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'save_fiche_audit') {
|
||||||
|
if (!fxAdminCanEditFicheAudit()) {
|
||||||
|
header('Location: fiche_audit.php?err=' . urlencode('Modification réservée.'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$arrResult = fxFicheAuditSaveConfig($_POST);
|
||||||
|
if ($arrResult['state'] === 'ok') {
|
||||||
|
header('Location: fiche_audit.php?ok=' . urlencode($arrResult['message']));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$strError = $arrResult['message'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($_GET['ok'])) {
|
||||||
|
$strFlash = (string)$_GET['ok'];
|
||||||
|
}
|
||||||
|
if (!empty($_GET['err'])) {
|
||||||
|
$strError = (string)$_GET['err'];
|
||||||
|
}
|
||||||
|
|
||||||
|
include('inc_header.php');
|
||||||
|
?>
|
||||||
|
<div class="container-fluid" id="main">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-9 col-lg-10 order-first order-md-last py-3">
|
||||||
|
<?php
|
||||||
|
require_once dirname(__FILE__) . '/../php/inc_fx_superadm_v2_ui.php';
|
||||||
|
fxSuperadmV2PanelOpen('Traçabilité fiche — Champs & rétention', array(
|
||||||
|
'zone_class' => 'superadm-content-v2-zone superadm-content-v2-zone--flush',
|
||||||
|
));
|
||||||
|
fxFicheAuditAdminRenderPage($strFlash, $strError);
|
||||||
|
fxSuperadmV2PanelClose();
|
||||||
|
?>
|
||||||
|
<p> </p>
|
||||||
|
</div>
|
||||||
|
<?php require_once('inc_droite.php'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php require_once('inc_footer.php'); ?>
|
||||||
@ -5,6 +5,7 @@ $strSidebarScript = basename($_SERVER['SCRIPT_NAME'] ?? '');
|
|||||||
$strEveAccesP = $_GET['p'] ?? 'kits';
|
$strEveAccesP = $_GET['p'] ?? 'kits';
|
||||||
$blnEveAccesActive = ($strSidebarScript === 'eve_acces.php');
|
$blnEveAccesActive = ($strSidebarScript === 'eve_acces.php');
|
||||||
$blnEveAccesKits = $blnEveAccesActive && in_array($strEveAccesP, array('kits', 'kit', 'assignations'), true);
|
$blnEveAccesKits = $blnEveAccesActive && in_array($strEveAccesP, array('kits', 'kit', 'assignations'), true);
|
||||||
|
$blnFicheAuditActive = ($strSidebarScript === 'fiche_audit.php');
|
||||||
$blnCtApiActive = ($strSidebarScript === 'chronotrack_api.php');
|
$blnCtApiActive = ($strSidebarScript === 'chronotrack_api.php');
|
||||||
$blnDocActive = ($strSidebarScript === 'documentation.php');
|
$blnDocActive = ($strSidebarScript === 'documentation.php');
|
||||||
// Sync BD — HTTP_HOST direct (évite échec des global $vbln… selon le contexte d'inclusion)
|
// Sync BD — HTTP_HOST direct (évite échec des global $vbln… selon le contexte d'inclusion)
|
||||||
@ -245,6 +246,18 @@ $blnSidebarStaticSync = !empty($_SESSION['usa_id'])
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="superadm-sidebar-v2-panel__group">
|
||||||
|
<div class="superadm-sidebar-v2-panel__label">Traçabilité fiche</div>
|
||||||
|
<ul class="nav nav-pills flex-column superadm-sidebar-v2-panel__nav">
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link<?php if ($blnFicheAuditActive) { ?> active<?php } ?>"
|
||||||
|
href="<?php echo $vDomaine; ?>/superadm/fiche_audit.php">
|
||||||
|
<i class="fa fa-history" aria-hidden="true"></i> Champs audités & rétention
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="superadm-sidebar-v2-panel__group">
|
<div class="superadm-sidebar-v2-panel__group">
|
||||||
<div class="superadm-sidebar-v2-panel__label">ChronoTrack</div>
|
<div class="superadm-sidebar-v2-panel__label">ChronoTrack</div>
|
||||||
<ul class="nav nav-pills flex-column superadm-sidebar-v2-panel__nav">
|
<ul class="nav nav-pills flex-column superadm-sidebar-v2-panel__nav">
|
||||||
|
|||||||
140
superadm/php/inc_fx_fiche_audit_admin.php
Normal file
140
superadm/php/inc_fx_fiche_audit_admin.php
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* MSIN-4464 — Super admin : gestion whitelist + rétention traçabilité fiche.
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once dirname(__DIR__, 2) . '/php/inc_fx_fiche_audit.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $strFlash
|
||||||
|
* @param string $strError
|
||||||
|
*/
|
||||||
|
function fxFicheAuditAdminRenderPage($strFlash = '', $strError = '')
|
||||||
|
{
|
||||||
|
$blnCanEdit = fxAdminCanEditFicheAudit();
|
||||||
|
$blnTablesOk = fxFicheAuditTablesReady();
|
||||||
|
$arrSettings = fxFicheAuditGetSettings();
|
||||||
|
$arrFields = fxFicheAuditGetFieldsForAdmin();
|
||||||
|
$strDisabled = ($blnCanEdit && $blnTablesOk) ? '' : ' disabled';
|
||||||
|
|
||||||
|
if ($strFlash !== '') {
|
||||||
|
echo '<div class="alert alert-success">' . htmlspecialchars($strFlash, ENT_QUOTES, 'UTF-8') . '</div>';
|
||||||
|
}
|
||||||
|
if ($strError !== '') {
|
||||||
|
echo '<div class="alert alert-danger">' . htmlspecialchars($strError, ENT_QUOTES, 'UTF-8') . '</div>';
|
||||||
|
}
|
||||||
|
if (!$blnTablesOk) {
|
||||||
|
echo '<div class="alert alert-warning">'
|
||||||
|
. 'Tables absentes. Exécuter <code>sql/MSIN-4464-fiche-audit-config.sql</code> puis recharger.'
|
||||||
|
. '</div>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
|
<h1 class="mb-0">Traçabilité fiche</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!$blnCanEdit) { ?>
|
||||||
|
<div class="alert alert-info py-2">
|
||||||
|
Lecture seule : vous pouvez consulter la configuration. Modification réservée.
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<div class="card mb-3 border-info">
|
||||||
|
<div class="card-header py-2 bg-light">
|
||||||
|
<strong><i class="fa fa-info-circle text-info"></i> Comment ça marche ?</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body py-3 small">
|
||||||
|
<ul class="mb-2 pl-3">
|
||||||
|
<li><strong>Whitelist</strong> — seuls les champs cochés seront historisés en détail (ancienne → nouvelle valeur + personne).</li>
|
||||||
|
<li><strong>Dernière intervention</strong> — si activée, on note aussi qui a touché la fiche, même pour un champ hors whitelist.</li>
|
||||||
|
<li><strong>Rétention</strong> — on ne garde pas un log sans fin : durée max + nombre max d'entrées par champ × participant.</li>
|
||||||
|
</ul>
|
||||||
|
<p class="mb-0 text-muted">
|
||||||
|
L'affichage « ? » sur la fiche (qui a touché ce champ) et le droit promoteur associé viendront dans une étape suivante.
|
||||||
|
Pour l'instant, cette page configure uniquement ce qui pourra être tracé.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" action="fiche_audit.php"<?= ($blnCanEdit && $blnTablesOk) ? '' : ' onsubmit="return false;"' ?>>
|
||||||
|
<input type="hidden" name="action" value="save_fiche_audit">
|
||||||
|
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header py-2">
|
||||||
|
<strong>Rétention</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-4">
|
||||||
|
<label for="retention_days">Conserver pendant (jours)</label>
|
||||||
|
<input type="number" class="form-control" id="retention_days" name="retention_days"
|
||||||
|
min="1" max="3650" value="<?= intval($arrSettings['retention_days']) ?>"<?= $strDisabled ?>>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-4">
|
||||||
|
<label for="retention_max_per_field">Max d'entrées / champ / participant</label>
|
||||||
|
<input type="number" class="form-control" id="retention_max_per_field" name="retention_max_per_field"
|
||||||
|
min="1" max="100" value="<?= intval($arrSettings['retention_max_per_field']) ?>"<?= $strDisabled ?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="custom-control custom-checkbox">
|
||||||
|
<input type="checkbox" class="custom-control-input" id="track_last_touch" name="track_last_touch" value="1"
|
||||||
|
<?= intval($arrSettings['track_last_touch']) === 1 ? ' checked' : '' ?><?= $strDisabled ?>>
|
||||||
|
<label class="custom-control-label" for="track_last_touch">
|
||||||
|
Noter aussi la dernière personne qui a touché la fiche (sans détail de champ)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header py-2">
|
||||||
|
<strong>Whitelist — champs à tracer en détail</strong>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-hover mb-0">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width:4rem;">Actif</th>
|
||||||
|
<th>Champ</th>
|
||||||
|
<th>Clé technique</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($arrFields as $arrField) {
|
||||||
|
$strKey = (string)$arrField['field_key'];
|
||||||
|
$strId = 'field_' . preg_replace('/[^a-z0-9_]/i', '_', $strKey);
|
||||||
|
$blnActif = (intval($arrField['field_actif']) === 1);
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center align-middle">
|
||||||
|
<input type="checkbox" id="<?= htmlspecialchars($strId, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
name="field_actif[]"
|
||||||
|
value="<?= htmlspecialchars($strKey, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
|
<?= $blnActif ? ' checked' : '' ?><?= $strDisabled ?>>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<label class="mb-0" for="<?= htmlspecialchars($strId, ENT_QUOTES, 'UTF-8') ?>">
|
||||||
|
<?= htmlspecialchars($arrField['field_label_fr'], ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</label>
|
||||||
|
<div class="text-muted small">
|
||||||
|
<?= htmlspecialchars($arrField['field_label_en'], ENT_QUOTES, 'UTF-8') ?>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="align-middle"><code><?= htmlspecialchars($strKey, ENT_QUOTES, 'UTF-8') ?></code></td>
|
||||||
|
</tr>
|
||||||
|
<?php } ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($blnCanEdit && $blnTablesOk) { ?>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fa fa-save" aria-hidden="true"></i> Enregistrer
|
||||||
|
</button>
|
||||||
|
<?php } ?>
|
||||||
|
</form>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user