Implement dashboard anomalies feature for bib management

This commit introduces a new functionality to handle dashboard anomalies related to bibs, including duplicate and orphaned bibs. The PHP code is updated to include a new AJAX action for collecting anomalies, while JavaScript functions are added to refresh the anomalies panel dynamically. Additionally, new CSS styles are introduced to enhance the layout and presentation of the anomalies information. The version code is incremented to 4.72.665 to reflect these changes, improving the overall user experience in managing bib assignments.
This commit is contained in:
2026-06-18 10:42:27 -04:00
parent 42bd7c36de
commit 8258e7ad29
6 changed files with 540 additions and 4 deletions

View File

@ -773,6 +773,37 @@ if ($action == 'save_auto_config') {
exit;
}
// MSIN-4379 — Dashboard anomalies (dossards en double, orphelins, etc.)
if ($action == 'anomalies') {
if (!fxBibRequirePromoteurSession()) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_session_invalid')
]);
exit;
}
$int_eve_id = (int)($_POST['eve_id'] ?? $_GET['eve_id'] ?? 0);
if ($int_eve_id <= 0) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid')
]);
exit;
}
$tabAnomalies = fxBibCollectAnomalies($int_eve_id, $strLangue);
echo json_encode([
'success' => true,
'count' => fxBibAnomalyTotalCount($tabAnomalies),
'html' => renderBibAnomaliesPanel($int_eve_id, $strLangue, $tabAnomalies),
]);
exit;
}
// =====================
// ACTION INVALIDE
// =====================

View File

@ -2157,3 +2157,102 @@ a.ms1-trad-link.btn-aide-trad{
.team-mode-option input[type="radio"]{
margin-top:2px;
}
/* MSIN-4379 — Dashboard anomalies dossards */
.bib-tool-card-header{
display:flex;
align-items:center;
justify-content:space-between;
gap:12px;
flex-wrap:wrap;
}
.bib-tool-card-title{
margin:0;
}
.bib-anomalies-panel{
margin-bottom:14px;
border:1px solid #ced4da;
border-radius:4px;
overflow:hidden;
}
.bib-anomalies-panel--alert{
border-color:#dc3545;
}
.bib-anomalies-header{
display:flex;
align-items:center;
justify-content:space-between;
gap:10px;
}
.bib-anomalies-header-label{
display:inline-flex;
align-items:center;
gap:8px;
flex-wrap:wrap;
}
.bib-anomalies-count{
font-size:12px;
font-weight:700;
}
.bib-anomalies-content{
background:#fff;
}
.bib-anomaly-block{
padding:10px 12px;
border-top:1px solid #e9ecef;
}
.bib-anomaly-block:first-child{
border-top:none;
}
.bib-anomaly-block-title{
display:flex;
align-items:center;
flex-wrap:wrap;
gap:8px;
margin:0 0 8px;
font-size:14px;
font-weight:700;
color:#212529;
}
.bib-anomaly-list{
margin:0;
}
.bib-anomaly-item{
padding:8px 10px;
margin-bottom:6px;
background:#f8f9fa;
border:1px solid #e9ecef;
border-radius:4px;
font-size:13px;
}
.bib-anomaly-item:last-child{
margin-bottom:0;
}
.bib-anomaly-sublist{
margin-top:4px;
padding-left:14px;
font-size:12px;
color:#495057;
}
.bib-anomaly-detail{
margin-top:4px;
}
.bib-anomalies-empty{
font-size:13px;
}

View File

@ -2167,6 +2167,35 @@ if ($strLangue == 'fr') {
return row.querySelectorAll('.batch-select-range:checked').length;
}
/** MSIN-4379 — Rafraîchit le dashboard anomalies (chien de garde). */
function refreshBibAnomaliesPanel() {
let mount = document.getElementById('bib-anomalies-mount');
if (!mount || !moduleBib) {
return;
}
let eveId = moduleBib.dataset.eveId || '';
if (!eveId) {
return;
}
fetch('/ajax_bib_range.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: 'action=anomalies&eve_id=' + encodeURIComponent(eveId) + bibAjaxLangSuffix()
})
.then(function (res) { return res.json(); })
.then(function (data) {
if (!data.success || !data.html) {
return;
}
mount.innerHTML = data.html;
initModuleBibTippy(mount);
});
}
function setBibContainerHtml(row, html) {
let container = getBibContainer(row);
if (!container) return null;
@ -2181,6 +2210,7 @@ if ($strLangue == 'fr') {
initModuleBibTippy(row);
}
syncBibUiState(row);
refreshBibAnomaliesPanel();
return container;
}

View File

@ -3543,11 +3543,15 @@ function fxShowBibTool4($str_code, $int_eve_id, $strLangue){
?>
<div class="card-header">
<h2><?php afficheTexte('promoteur_bib_tool')?></h2>
<div class="card-header bib-tool-card-header">
<h2 class="bib-tool-card-title"><?php afficheTexte('promoteur_bib_tool')?></h2>
</div>
<div id="module-bib" class="epr-wrapper" data-csrf-token="<?php echo fxBibEsc(fxBibCsrfToken()); ?>" data-lang="<?php echo fxBibEsc($strLangue); ?>"<?php echo fxBibModuleJsDataAttrs(); ?><?php echo fxAdminTextEditModeActive() ? ' data-bib-trad="1"' : ''; ?>>
<div id="module-bib" class="epr-wrapper" data-csrf-token="<?php echo fxBibEsc(fxBibCsrfToken()); ?>" data-lang="<?php echo fxBibEsc($strLangue); ?>" data-eve-id="<?php echo (int)$int_eve_id; ?>"<?php echo fxBibModuleJsDataAttrs(); ?><?php echo fxAdminTextEditModeActive() ? ' data-bib-trad="1"' : ''; ?>>
<div id="bib-anomalies-mount">
<?php echo renderBibAnomaliesPanel((int)$int_eve_id, $strLangue); ?>
</div>
<?php for($i = 1; $i <= count($tabEpreuves); $i++){ ?>
@ -3863,6 +3867,313 @@ function fxInfosBibRange($epr_id = 0, $epr_bib_id = 0){
return $objDatabase->fxGetResults($sqlBibStats);
}
/** MSIN-4379 — Libellé court épreuve pour le dashboard anomalies. */
function fxBibAnomalyEprLabel(array $tabEpreuve, $strLangue = 'fr') {
$str = fxBibEpreuveDisplayName($tabEpreuve, $strLangue);
$strNom = trim($tabEpreuve['epr_nom_' . $strLangue] ?? '');
if ($strNom !== '' && stripos($str, $strNom) === false) {
$str .= ' — ' . $strNom;
}
return $str;
}
/**
* MSIN-4379 — Dossards en double dans une séquence (même epr_id, même no_bib, >1 participant actif).
* @return array<int, array<string, mixed>>
*/
function fxBibAnomalyDuplicateBibs($int_eve_id, $strLangue = 'fr') {
global $objDatabase;
$int_eve_id = (int)$int_eve_id;
if ($int_eve_id <= 0) {
return [];
}
$sqlBibNumP = fxBibSqlNoBibUnsigned('p.no_bib');
$sql = "
SELECT
b.epr_bib_id,
b.epr_id,
b.epr_bib_start,
b.epr_bib_finish,
e.epr_nom_fr,
e.epr_nom_en,
e.epr_type_fr,
e.epr_type_en,
e.epr_categorie_fr,
e.epr_categorie_en,
e.epr_actif,
$sqlBibNumP AS no_bib,
p.par_id,
p.par_prenom,
p.par_nom
FROM inscriptions_epreuves_bib b
INNER JOIN inscriptions_epreuves e ON e.epr_id = b.epr_id
INNER JOIN resultats_participants p
ON p.epr_id = b.epr_id
AND p.is_cancelled = 0
AND $sqlBibNumP BETWEEN b.epr_bib_start AND b.epr_bib_finish
AND $sqlBibNumP > 0
WHERE e.eve_id = $int_eve_id
AND b.epr_bib_start IS NOT NULL
AND b.epr_bib_finish IS NOT NULL
AND (
SELECT COUNT(*)
FROM resultats_participants p2
WHERE p2.epr_id = b.epr_id
AND p2.is_cancelled = 0
AND " . fxBibSqlNoBibUnsigned('p2.no_bib') . " = $sqlBibNumP
) > 1
ORDER BY b.epr_id, b.epr_bib_start, $sqlBibNumP, p.par_nom, p.par_prenom
";
$rows = $objDatabase->fxGetResults($sql);
if (!is_array($rows) || empty($rows)) {
return [];
}
$grouped = [];
foreach ($rows as $row) {
$key = (int)$row['epr_bib_id'] . '-' . (int)$row['no_bib'];
if (!isset($grouped[$key])) {
$grouped[$key] = [
'epr_id' => (int)$row['epr_id'],
'epr_label' => fxBibAnomalyEprLabel($row, $strLangue),
'range_id' => (int)$row['epr_bib_id'],
'range_start' => (int)$row['epr_bib_start'],
'range_end' => (int)$row['epr_bib_finish'],
'no_bib' => (int)$row['no_bib'],
'participants' => [],
];
}
$grouped[$key]['participants'][] = [
'par_id' => (int)$row['par_id'],
'par_prenom' => trim($row['par_prenom'] ?? ''),
'par_nom' => trim($row['par_nom'] ?? ''),
];
}
return array_values($grouped);
}
/**
* MSIN-4379 — Dossards orphelins : numéro dans une séquence mais plus valide pour l'épreuve
* (coureur annulé, ou dossard conservé après changement d'épreuve).
* @return array<int, array<string, mixed>>
*/
function fxBibAnomalyOrphanBibs($int_eve_id, $strLangue = 'fr') {
global $objDatabase;
$int_eve_id = (int)$int_eve_id;
if ($int_eve_id <= 0) {
return [];
}
$sqlBibNumP = fxBibSqlNoBibUnsigned('p.no_bib');
$sql = "
SELECT
b.epr_bib_id,
b.epr_id,
b.epr_bib_start,
b.epr_bib_finish,
e.epr_nom_fr,
e.epr_nom_en,
e.epr_type_fr,
e.epr_type_en,
e.epr_categorie_fr,
e.epr_categorie_en,
e.epr_actif,
$sqlBibNumP AS no_bib,
p.par_id,
p.par_prenom,
p.par_nom,
p.epr_id AS participant_epr_id,
p.is_cancelled,
CASE
WHEN p.epr_id <> b.epr_id THEN 'transferred'
WHEN p.is_cancelled = 1 THEN 'cancelled'
ELSE 'other'
END AS orphan_type
FROM inscriptions_epreuves_bib b
INNER JOIN inscriptions_epreuves e ON e.epr_id = b.epr_id
INNER JOIN resultats_participants p
ON p.eve_id = e.eve_id
AND $sqlBibNumP > 0
AND $sqlBibNumP BETWEEN b.epr_bib_start AND b.epr_bib_finish
WHERE e.eve_id = $int_eve_id
AND b.epr_bib_start IS NOT NULL
AND b.epr_bib_finish IS NOT NULL
AND (
(p.epr_id <> b.epr_id)
OR (p.epr_id = b.epr_id AND p.is_cancelled = 1)
)
ORDER BY b.epr_id, b.epr_bib_start, $sqlBibNumP, p.par_nom, p.par_prenom
";
$rows = $objDatabase->fxGetResults($sql);
if (!is_array($rows) || empty($rows)) {
return [];
}
$out = [];
foreach ($rows as $row) {
$participantEprId = (int)$row['participant_epr_id'];
$participantLabel = '';
if ($participantEprId > 0) {
$tabHolderEpr = $objDatabase->fxGetRow("
SELECT epr_id, epr_nom_fr, epr_nom_en, epr_type_fr, epr_type_en,
epr_categorie_fr, epr_categorie_en, epr_actif
FROM inscriptions_epreuves
WHERE epr_id = $participantEprId
LIMIT 1
");
if (is_array($tabHolderEpr)) {
$participantLabel = fxBibAnomalyEprLabel($tabHolderEpr, $strLangue);
}
}
$out[] = [
'epr_id' => (int)$row['epr_id'],
'epr_label' => fxBibAnomalyEprLabel($row, $strLangue),
'range_id' => (int)$row['epr_bib_id'],
'range_start' => (int)$row['epr_bib_start'],
'range_end' => (int)$row['epr_bib_finish'],
'no_bib' => (int)$row['no_bib'],
'orphan_type' => $row['orphan_type'],
'participant_epr_id' => $participantEprId,
'participant_epr_label'=> $participantLabel,
'par_id' => (int)$row['par_id'],
'par_prenom' => trim($row['par_prenom'] ?? ''),
'par_nom' => trim($row['par_nom'] ?? ''),
];
}
return $out;
}
/** MSIN-4379 — Agrège toutes les anomalies dossards pour un événement. */
function fxBibCollectAnomalies($int_eve_id, $strLangue = 'fr') {
return [
'dupes' => fxBibAnomalyDuplicateBibs($int_eve_id, $strLangue),
'orphans' => fxBibAnomalyOrphanBibs($int_eve_id, $strLangue),
];
}
function fxBibAnomalyTotalCount(array $tabAnomalies) {
$intTotal = 0;
foreach (['dupes', 'orphans'] as $strKey) {
if (!empty($tabAnomalies[$strKey]) && is_array($tabAnomalies[$strKey])) {
$intTotal += count($tabAnomalies[$strKey]);
}
}
return $intTotal;
}
/** MSIN-4379 — Panneau dashboard anomalies (HTML). */
function renderBibAnomaliesPanel($int_eve_id, $strLangue = 'fr', $tabAnomalies = null) {
$int_eve_id = (int)$int_eve_id;
if ($tabAnomalies === null) {
$tabAnomalies = fxBibCollectAnomalies($int_eve_id, $strLangue);
}
$intCount = fxBibAnomalyTotalCount($tabAnomalies);
$blnHasIssues = $intCount > 0;
ob_start();
?>
<div id="bib-anomalies-panel"
class="bib-anomalies-panel epr-block<?php echo $blnHasIssues ? ' bib-anomalies-panel--alert' : ''; ?> is-collapsed"
data-eve-id="<?php echo $int_eve_id; ?>">
<div class="epr-header bib-anomalies-header">
<span class="epr-header-label bib-anomalies-header-label">
<?php echo fxBibEsc(fxBibTexte('bib_v4_anomalies_title', 0)); ?>
<?php echo fxBibAideButton('bib_v4_anomalies_title'); ?>
<span class="bib-anomalies-count badge<?php echo $blnHasIssues ? ' badge-danger' : ' badge-success'; ?>">
<?php echo (int)$intCount; ?>
</span>
</span>
<?php echo fxBibBlockCollapseToggle($strLangue, true); ?>
</div>
<div class="epr-content bib-anomalies-content">
<?php if (!$blnHasIssues) { ?>
<p class="bib-anomalies-empty text-muted mb-0">
<?php fxBibTexte('bib_v4_anomalies_none', 1); ?>
</p>
<?php } else { ?>
<?php if (!empty($tabAnomalies['dupes'])) { ?>
<section class="bib-anomaly-block bib-anomaly-block--dupes">
<h3 class="bib-anomaly-block-title">
<?php fxBibTexte('bib_v4_anomalies_dupes_title', 1); ?>
<?php echo fxBibAideButton('bib_v4_anomalies_dupes_title'); ?>
<span class="badge badge-secondary"><?php echo count($tabAnomalies['dupes']); ?></span>
</h3>
<ul class="bib-anomaly-list list-unstyled mb-0">
<?php foreach ($tabAnomalies['dupes'] as $item) { ?>
<li class="bib-anomaly-item">
<strong><?php echo fxBibEsc($item['epr_label']); ?></strong>
— <?php echo fxBibEsc(sprintf(
fxBibTexte('bib_v4_anomalies_range_fmt', 0),
(int)$item['range_start'],
(int)$item['range_end']
)); ?>
— <?php echo fxBibEsc(sprintf(
fxBibTexte('bib_v4_anomalies_bib_fmt', 0),
(int)$item['no_bib']
)); ?>
<ul class="bib-anomaly-sublist list-unstyled mb-0">
<?php foreach ($item['participants'] as $p) { ?>
<li><?php echo fxBibEsc(trim($p['par_prenom'] . ' ' . $p['par_nom'])); ?></li>
<?php } ?>
</ul>
</li>
<?php } ?>
</ul>
</section>
<?php } ?>
<?php if (!empty($tabAnomalies['orphans'])) { ?>
<section class="bib-anomaly-block bib-anomaly-block--orphans">
<h3 class="bib-anomaly-block-title">
<?php fxBibTexte('bib_v4_anomalies_orphans_title', 1); ?>
<?php echo fxBibAideButton('bib_v4_anomalies_orphans_title'); ?>
<span class="badge badge-secondary"><?php echo count($tabAnomalies['orphans']); ?></span>
</h3>
<ul class="bib-anomaly-list list-unstyled mb-0">
<?php foreach ($tabAnomalies['orphans'] as $item) {
$strTypeKey = $item['orphan_type'] === 'transferred'
? 'bib_v4_anomalies_orphan_transferred'
: 'bib_v4_anomalies_orphan_cancelled';
?>
<li class="bib-anomaly-item">
<strong><?php echo fxBibEsc($item['epr_label']); ?></strong>
— <?php echo fxBibEsc(sprintf(
fxBibTexte('bib_v4_anomalies_range_fmt', 0),
(int)$item['range_start'],
(int)$item['range_end']
)); ?>
— <?php echo fxBibEsc(sprintf(
fxBibTexte('bib_v4_anomalies_bib_fmt', 0),
(int)$item['no_bib']
)); ?>
<div class="bib-anomaly-detail text-muted small">
<?php echo fxBibEsc(trim($item['par_prenom'] . ' ' . $item['par_nom'])); ?>
— <?php echo fxBibEsc(fxBibTexte($strTypeKey, 0)); ?>
<?php if ($item['orphan_type'] === 'transferred' && $item['participant_epr_label'] !== '') { ?>
(<?php echo fxBibEsc($item['participant_epr_label']); ?>)
<?php } ?>
</div>
</li>
<?php } ?>
</ul>
</section>
<?php } ?>
<?php } ?>
</div>
</div>
<?php
return ob_get_clean();
}
/**
* MSIN-4379 — Étape 2
* Indique si l'assignation automatique est active pour une épreuve

View File

@ -7,7 +7,7 @@
* Constantes *
*
**************/
define('_VERSION_CODE', '4.72.664');
define('_VERSION_CODE', '4.72.665');
define('_DATE_CODE', '2026-06-18');
//MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');

View File

@ -0,0 +1,65 @@
-- MSIN-4379 — Dashboard anomalies dossards (i18n)
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_title', 'fr', 'Anomalies', 'Tableau de bord des problèmes de dossards détectés sur l''événement (doublons, orphelins, etc.).', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_title' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_title', 'en', 'Anomalies', 'Dashboard of bib issues detected for the event (duplicates, orphans, etc.).', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_title' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_none', 'fr', 'Aucune anomalie détectée.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_none' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_none', 'en', 'No issues detected.', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_none' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_dupes_title', 'fr', 'Dossards en double', 'Même numéro assigné à plusieurs coureurs actifs dans une séquence.', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_dupes_title' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_dupes_title', 'en', 'Duplicate bib numbers', 'Same number assigned to multiple active runners in a sequence.', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_dupes_title' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_orphans_title', 'fr', 'Dossards orphelins', 'Numéro dans une séquence mais plus valide pour l''épreuve (coureur annulé ou changement d''épreuve).', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_orphans_title' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_orphans_title', 'en', 'Orphan bib numbers', 'Number falls in a sequence but is no longer valid for the race (cancelled runner or race change).', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_orphans_title' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_range_fmt', 'fr', 'Séquence %d%d', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_range_fmt' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_range_fmt', 'en', 'Sequence %d%d', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_range_fmt' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_bib_fmt', 'fr', 'Dossard #%d', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_bib_fmt' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_bib_fmt', 'en', 'Bib #%d', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_bib_fmt' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_orphan_transferred', 'fr', 'Dossard conservé après changement d''épreuve', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_orphan_transferred' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_orphan_transferred', 'en', 'Bib kept after race change', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_orphan_transferred' AND info_langue = 'en' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_orphan_cancelled', 'fr', 'Coureur annulé avec dossard encore présent', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_orphan_cancelled' AND info_langue = 'fr' AND info_prg = 'compte.php');
INSERT INTO info (info_clef, info_langue, info_texte, info_aide, info_prg, info_description, info_trie, info_actif, info_option1, info_option2, info_option3, info_creation)
SELECT 'bib_v4_anomalies_orphan_cancelled', 'en', 'Cancelled runner still has a bib number', '', 'compte.php', '', 0, 1, '', '', '', NOW()
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM info WHERE info_clef = 'bib_v4_anomalies_orphan_cancelled' AND info_langue = 'en' AND info_prg = 'compte.php');