MSIN-4579 — Implemented manual milestone management features, including the ability to mark milestones as done and view their history. Updated PHP functions to handle new actions and added corresponding AJAX endpoints. Enhanced the UI with new CSS styles for better usability and added localization for new dialog elements. Incremented version to 4.73.041 to reflect these changes.

This commit is contained in:
2026-08-20 13:02:32 -04:00
parent a9f2f1128f
commit 97701b5fa3
8 changed files with 573 additions and 30 deletions

View File

@ -55,6 +55,8 @@ $arrBibAjaxActions = [
'race_detail',
'production_panel',
'commande_milestones_save',
'commande_milestone_done',
'commande_milestone_history',
'commande_segment_save',
'commande_segment_note_save',
'commande_file_note_save',
@ -1537,6 +1539,53 @@ if ($action == 'commande_milestones_save') {
exit;
}
// MSIN-4579 — Réalité jalon manuelle (date/heure + qui)
if ($action == 'commande_milestone_done') {
$int_eve_id = (int)($_POST['eve_id'] ?? 0);
$strKind = (string)($_POST['kind'] ?? '');
if ($int_eve_id <= 0 || !function_exists('fxBibCommandeStampMilestoneDone')) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid'),
]);
exit;
}
if (!fxBibOpsUserCanAccessCommande($int_eve_id)) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_unauthorized'),
]);
exit;
}
echo json_encode(fxBibCommandeStampMilestoneDone($int_eve_id, $strKind));
exit;
}
// MSIN-4579 — Historique réalité jalon (comme champs de changement)
if ($action == 'commande_milestone_history') {
$int_eve_id = (int)($_POST['eve_id'] ?? 0);
$strKind = (string)($_POST['kind'] ?? '');
if ($int_eve_id <= 0 || !function_exists('fxBibCommandeMilestoneHistory')) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_epr_invalid'),
]);
exit;
}
if (!fxBibOpsUserCanAccessCommande($int_eve_id)) {
echo json_encode([
'success' => false,
'message' => fxBibMsg('bib_v4_ajax_unauthorized'),
]);
exit;
}
echo json_encode([
'success' => true,
'items' => fxBibCommandeMilestoneHistory($int_eve_id, $strKind),
]);
exit;
}
// MSIN-4577 — Drapeaux commande puces / dossards par séquence
if ($action == 'commande_segment_save') {
$int_eve_id = (int)($_POST['eve_id'] ?? 0);

View File

@ -7370,7 +7370,7 @@ button.inscr-gestion-list-remis:hover{
}
.bib-cmd-jalon{
display:grid;
grid-template-columns:minmax(180px,1.4fr) minmax(140px,0.8fr) minmax(120px,0.7fr);
grid-template-columns:minmax(180px,1.4fr) minmax(140px,0.8fr) minmax(200px,1fr);
gap:12px 16px;
align-items:start;
padding:10px 0;
@ -7381,8 +7381,52 @@ button.inscr-gestion-list-remis:hover{
flex-direction:column;
gap:4px;
}
.bib-cmd-realite-box{
display:flex;
flex-direction:column;
align-items:flex-start;
gap:6px;
}
.bib-cmd-realite{
font-weight:600;
display:flex;
align-items:center;
flex-wrap:wrap;
gap:2px 0;
}
.bib-cmd-realite-by{
font-size:12px;
font-weight:400;
color:#6b7785;
}
.bib-cmd-audit-dialog{
position:fixed;
inset:0;
z-index:1080;
display:flex;
align-items:center;
justify-content:center;
padding:16px;
background:rgba(20,28,36,0.45);
}
.bib-cmd-audit-dialog[hidden]{
display:none;
}
.bib-cmd-audit-dialog-card{
width:min(640px,100%);
max-height:80vh;
overflow:auto;
background:#fff;
border-radius:8px;
padding:16px 18px;
box-shadow:0 12px 32px rgba(0,0,0,0.18);
}
.bib-cmd-audit-dialog-card h2{
font-size:1.1rem;
margin:0 0 12px;
}
.bib-cmd-audit-dialog-body{
margin-bottom:12px;
}
.bib-cmd-files{
display:grid;

View File

@ -51,7 +51,139 @@
}).then(function (res) { return res.json(); });
}
function cmdEsc(str) {
return String(str == null ? '' : str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
function cmdEnsureBang(jalon) {
if (!jalon) {
return;
}
var wrap = jalon.querySelector('.bib-cmd-realite');
if (!wrap || wrap.querySelector('.bib-cmd-realite-bang')) {
return;
}
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'inscr-gestion-audit-bang bib-cmd-realite-bang';
btn.setAttribute('data-kind', jalon.getAttribute('data-kind') || '');
btn.setAttribute('title', page.getAttribute('data-bang-title') || '');
btn.setAttribute('aria-label', page.getAttribute('data-bang-aria') || '');
btn.textContent = '!';
wrap.appendChild(btn);
}
function cmdAuditDialog() {
return document.getElementById('bib-cmd-audit-dialog');
}
function cmdCloseAudit() {
var dlg = cmdAuditDialog();
if (dlg) {
dlg.hidden = true;
}
}
function cmdShowAudit(items) {
var dlg = cmdAuditDialog();
if (!dlg) {
return;
}
var title = dlg.querySelector('#bib-cmd-audit-dialog-title');
var body = dlg.querySelector('.bib-cmd-audit-dialog-body');
var closeBtn = dlg.querySelector('.bib-cmd-audit-dialog-close');
if (title) {
title.textContent = page.getAttribute('data-audit-title') || '';
}
if (closeBtn) {
closeBtn.textContent = page.getAttribute('data-audit-close') || '';
}
if (body) {
if (!items || !items.length) {
body.innerHTML = '<p class="mb-0">' + cmdEsc(page.getAttribute('data-audit-empty') || '') + '</p>';
} else {
var html = '<div class="table-responsive"><table class="table table-sm table-striped mb-0 small">';
html += '<thead><tr>'
+ '<th>' + cmdEsc(page.getAttribute('data-audit-when') || '') + '</th>'
+ '<th>' + cmdEsc(page.getAttribute('data-audit-who') || '') + '</th>'
+ '<th>' + cmdEsc(page.getAttribute('data-audit-from') || '') + '</th>'
+ '<th>' + cmdEsc(page.getAttribute('data-audit-to') || '') + '</th>'
+ '</tr></thead><tbody>';
for (var i = 0; i < items.length; i++) {
var row = items[i] || {};
html += '<tr>'
+ '<td class="text-nowrap">' + cmdEsc(row.when || '') + '</td>'
+ '<td>' + cmdEsc(row.who || '') + '</td>'
+ '<td>' + cmdEsc(row.old || '') + '</td>'
+ '<td>' + cmdEsc(row.new || '') + '</td>'
+ '</tr>';
}
html += '</tbody></table></div>';
body.innerHTML = html;
}
}
dlg.hidden = false;
}
page.addEventListener('click', function (e) {
var stamp = e.target.closest('.bib-cmd-realite-stamp');
if (stamp && page.contains(stamp)) {
e.preventDefault();
e.stopPropagation();
var jalonStamp = stamp.closest('.bib-cmd-jalon');
var kindStamp = jalonStamp ? (jalonStamp.getAttribute('data-kind') || '') : '';
cmdPost('action=commande_milestone_done&kind=' + encodeURIComponent(kindStamp))
.then(function (result) {
if (!result || !result.success) {
alert((result && result.message) || 'Erreur');
return;
}
if (jalonStamp) {
var whenEl = jalonStamp.querySelector('.bib-cmd-realite-when');
if (whenEl) {
whenEl.textContent = result.done_label || '';
}
var byEl = jalonStamp.querySelector('.bib-cmd-realite-by');
if (byEl) {
byEl.textContent = result.done_by || '';
byEl.hidden = !(result.done_by);
}
if (result.has_log) {
cmdEnsureBang(jalonStamp);
}
}
cmdSetStatus(page.getAttribute('data-label-saved') || '');
})
.catch(function () {
alert('Erreur');
});
return;
}
var bang = e.target.closest('.bib-cmd-realite-bang');
if (bang && page.contains(bang)) {
e.preventDefault();
e.stopPropagation();
var jalonBang = bang.closest('.bib-cmd-jalon');
var kindBang = jalonBang ? (jalonBang.getAttribute('data-kind') || '') : '';
cmdPost('action=commande_milestone_history&kind=' + encodeURIComponent(kindBang))
.then(function (result) {
if (!result || !result.success) {
alert((result && result.message) || 'Erreur');
return;
}
cmdShowAudit(result.items || []);
})
.catch(function () {
alert('Erreur');
});
return;
}
var head = e.target.closest('.bib-cmd-fold-head');
if (!head || !page.contains(head)) {
return;
@ -372,5 +504,19 @@
}, 2500);
});
var auditDlg = cmdAuditDialog();
if (auditDlg) {
auditDlg.addEventListener('click', function (e) {
if (e.target === auditDlg || e.target.closest('.bib-cmd-audit-dialog-close')) {
cmdCloseAudit();
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && !auditDlg.hidden) {
cmdCloseAudit();
}
});
}
cmdInitChooser();
}());

View File

@ -44,6 +44,45 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) {
return $blnCached;
}
function fxBibCommandeHasMilestoneDoneByColumn() {
global $objDatabase;
static $blnCached = null;
if ($blnCached !== null) {
return $blnCached;
}
$tabRow = $objDatabase->fxGetRow(
"SELECT COUNT(*) AS c
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'inscriptions_bib_commande_milestones'
AND COLUMN_NAME = 'bcm_done_by'"
);
$blnCached = ((int)($tabRow['c'] ?? 0) > 0);
return $blnCached;
}
function fxBibCommandeHasMilestoneLogTable() {
global $objDatabase;
static $blnCached = null;
if ($blnCached !== null) {
return $blnCached;
}
$tabRow = $objDatabase->fxGetRow(
"SELECT COUNT(*) AS c
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'inscriptions_bib_commande_milestone_log'"
);
$blnCached = ((int)($tabRow['c'] ?? 0) > 0);
return $blnCached;
}
function fxBibCommandeActorLabel() {
return function_exists('fxBibGetCurrentPromoteurDisplayName')
? trim((string)fxBibGetCurrentPromoteurDisplayName())
: '';
}
function fxBibCommandeHasSegmentFlagColumns() {
global $objDatabase;
static $blnCached = null;
@ -87,14 +126,19 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) {
}
/**
* @return array<string, array{due:?string, done_at:?string}>
* @return array<string, array{due:?string, done_at:?string, done_by:string, has_log:bool}>
*/
function fxBibCommandeLoadMilestones($intEveId) {
global $objDatabase;
$tabOut = [];
foreach (fxBibCommandeMilestoneKinds() as $strKind) {
$tabOut[$strKind] = ['due' => null, 'done_at' => null];
$tabOut[$strKind] = [
'due' => null,
'done_at' => null,
'done_by' => '',
'has_log' => false,
];
}
$intEveId = (int)$intEveId;
@ -102,8 +146,10 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) {
return $tabOut;
}
$blnDoneBy = fxBibCommandeHasMilestoneDoneByColumn();
$strBySql = $blnDoneBy ? ', bcm_done_by' : '';
$tabRows = $objDatabase->fxGetResults(
"SELECT bcm_kind, bcm_due, bcm_done_at
"SELECT bcm_kind, bcm_due, bcm_done_at $strBySql
FROM inscriptions_bib_commande_milestones
WHERE eve_id = $intEveId"
);
@ -122,8 +168,27 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) {
'done_at' => ($tabRow['bcm_done_at'] ?? '') !== '' && $tabRow['bcm_done_at'] !== '0000-00-00 00:00:00'
? (string)$tabRow['bcm_done_at']
: null,
'done_by' => $blnDoneBy ? trim((string)($tabRow['bcm_done_by'] ?? '')) : '',
'has_log' => false,
];
}
if (fxBibCommandeHasMilestoneLogTable()) {
$tabLogs = $objDatabase->fxGetResults(
"SELECT bcm_kind, COUNT(*) AS c
FROM inscriptions_bib_commande_milestone_log
WHERE eve_id = $intEveId
GROUP BY bcm_kind"
);
if (is_array($tabLogs)) {
foreach ($tabLogs as $tabLog) {
$strKind = fxBibCommandeNormalizeKind($tabLog['bcm_kind'] ?? '');
if ($strKind !== '' && (int)($tabLog['c'] ?? 0) > 0) {
$tabOut[$strKind]['has_log'] = true;
}
}
}
}
return $tabOut;
}
@ -149,9 +214,7 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) {
$mixDueSql = "'" . $objDatabase->fxEscape($strDue) . "'";
}
$strBy = function_exists('fxBibGetCurrentPromoteurDisplayName')
? fxBibGetCurrentPromoteurDisplayName()
: '';
$strBy = fxBibCommandeActorLabel();
$objDatabase->fxQuery(
"INSERT INTO inscriptions_bib_commande_milestones
@ -172,37 +235,145 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) {
}
/**
* Pose la date de réalité (génération fichier, plus tard Go visuel, etc.).
* MSIN-4579 — Pose manuelle de la réalité (date/heure + qui). Plus dauto
* à la génération / au blocage Excel.
* @return array{success:bool,message?:string,done_at?:string,done_label?:string,done_by?:string,has_log?:bool}
*/
function fxBibCommandeMarkMilestoneDone($intEveId, $strKind) {
function fxBibCommandeStampMilestoneDone($intEveId, $strKind) {
global $objDatabase;
$intEveId = (int)$intEveId;
$strKind = fxBibCommandeNormalizeKind($strKind);
if ($intEveId <= 0 || $strKind === '' || !fxBibCommandeHasMilestoneTable()) {
return false;
if ($intEveId <= 0 || $strKind === '') {
return ['success' => false, 'message' => fxBibMsg('bib_v4_ajax_epr_invalid')];
}
if (!fxBibCommandeHasMilestoneTable()) {
return ['success' => false, 'message' => fxBibTexte('bib_v5_cmd_sql_missing', 0)];
}
$strBy = function_exists('fxBibGetCurrentPromoteurDisplayName')
? fxBibGetCurrentPromoteurDisplayName()
: '';
$strBy = fxBibCommandeActorLabel();
$strKindSql = "'" . $objDatabase->fxEscape($strKind) . "'";
$strBySql = "'" . $objDatabase->fxEscape($strBy) . "'";
$blnDoneBy = fxBibCommandeHasMilestoneDoneByColumn();
$tabCur = $objDatabase->fxGetRow(
"SELECT bcm_done_at
FROM inscriptions_bib_commande_milestones
WHERE eve_id = $intEveId AND bcm_kind = $strKindSql
LIMIT 1"
);
$strOldRaw = '';
if ($tabCur) {
$strOldRaw = (string)($tabCur['bcm_done_at'] ?? '');
if ($strOldRaw === '0000-00-00 00:00:00') {
$strOldRaw = '';
}
}
$strOldDisp = $strOldRaw !== '' ? date('Y-m-d H:i', strtotime($strOldRaw)) : '';
$strDoneBySql = $blnDoneBy ? ", bcm_done_by" : '';
$strDoneByVal = $blnDoneBy ? ", $strBySql" : '';
$strDoneByUpd = $blnDoneBy ? ", bcm_done_by = VALUES(bcm_done_by)" : '';
$objDatabase->fxQuery(
"INSERT INTO inscriptions_bib_commande_milestones
(eve_id, bcm_kind, bcm_done_at, bcm_updated_at, bcm_updated_by)
(eve_id, bcm_kind, bcm_done_at, bcm_updated_at, bcm_updated_by $strDoneBySql)
VALUES (
$intEveId,
'" . $objDatabase->fxEscape($strKind) . "',
$strKindSql,
NOW(),
NOW(),
'" . $objDatabase->fxEscape($strBy) . "'
$strBySql
$strDoneByVal
)
ON DUPLICATE KEY UPDATE
bcm_done_at = IF(bcm_done_at IS NULL, NOW(), bcm_done_at),
bcm_done_at = NOW(),
bcm_updated_at = NOW(),
bcm_updated_by = VALUES(bcm_updated_by)"
bcm_updated_by = VALUES(bcm_updated_by)
$strDoneByUpd"
);
return true;
$tabNew = $objDatabase->fxGetRow(
"SELECT bcm_done_at
FROM inscriptions_bib_commande_milestones
WHERE eve_id = $intEveId AND bcm_kind = $strKindSql
LIMIT 1"
);
$strNewRaw = (string)($tabNew['bcm_done_at'] ?? '');
$strNewDisp = $strNewRaw !== '' && $strNewRaw !== '0000-00-00 00:00:00'
? date('Y-m-d H:i', strtotime($strNewRaw))
: '';
$blnHasLog = false;
if (fxBibCommandeHasMilestoneLogTable() && $strOldDisp !== $strNewDisp) {
$objDatabase->fxQuery(
"INSERT INTO inscriptions_bib_commande_milestone_log
(eve_id, bcm_kind, old_value, new_value, changed_by_label, created_at)
VALUES (
$intEveId,
$strKindSql,
'" . $objDatabase->fxEscape($strOldDisp) . "',
'" . $objDatabase->fxEscape($strNewDisp) . "',
$strBySql,
NOW()
)"
);
}
if (fxBibCommandeHasMilestoneLogTable()) {
$blnHasLog = ((int)$objDatabase->fxGetVar(
"SELECT COUNT(*)
FROM inscriptions_bib_commande_milestone_log
WHERE eve_id = $intEveId AND bcm_kind = $strKindSql"
) > 0);
}
return [
'success' => true,
'done_at' => $strNewRaw,
'done_label' => $strNewDisp !== ''
? $strNewDisp
: fxBibTexte('bib_v5_cmd_realite_empty', 0),
'done_by' => $strBy,
'has_log' => $blnHasLog,
];
}
/**
* Historique réalité dun jalon (même colonnes que les champs de changement).
* @return array<int, array{when:string,who:string,old:string,new:string}>
*/
function fxBibCommandeMilestoneHistory($intEveId, $strKind) {
global $objDatabase;
$intEveId = (int)$intEveId;
$strKind = fxBibCommandeNormalizeKind($strKind);
if ($intEveId <= 0 || $strKind === '' || !fxBibCommandeHasMilestoneLogTable()) {
return [];
}
$tabRows = $objDatabase->fxGetResults(
"SELECT old_value, new_value, changed_by_label, created_at
FROM inscriptions_bib_commande_milestone_log
WHERE eve_id = $intEveId
AND bcm_kind = '" . $objDatabase->fxEscape($strKind) . "'
ORDER BY created_at DESC, bcml_id DESC
LIMIT 20"
);
if (!is_array($tabRows)) {
return [];
}
$tabOut = [];
foreach ($tabRows as $tabRow) {
$strWhen = (string)($tabRow['created_at'] ?? '');
$intTs = strtotime($strWhen);
$tabOut[] = [
'when' => $intTs > 0 ? date('Y-m-d H:i', $intTs) : $strWhen,
'who' => trim((string)($tabRow['changed_by_label'] ?? '')),
'old' => trim((string)($tabRow['old_value'] ?? '')),
'new' => trim((string)($tabRow['new_value'] ?? '')),
];
}
return $tabOut;
}
/**
@ -517,16 +688,35 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) {
return '</div></section>';
}
function fxBibCommandeRenderRealiteBang($strKind) {
return ' <button type="button" class="inscr-gestion-audit-bang bib-cmd-realite-bang"'
. ' data-kind="' . fxBibEsc($strKind) . '"'
. ' title="' . fxBibEsc(fxBibTexte('bib_v5_cmd_realite_bang_title', 0)) . '"'
. ' aria-label="' . fxBibEsc(fxBibTexte('bib_v5_cmd_realite_bang_aria', 0)) . '">!</button>';
}
function fxBibCommandeRenderDatesBox(array $tabMilestones) {
$html = fxBibCommandeRenderFoldOpen(
'<span class="bib-cmd-n">1</span> ' . fxBibEsc(fxBibTexte('bib_v5_cmd_dates_title', 0))
);
$html .= '<p class="text-muted small">' . fxBibEsc(fxBibTexte('bib_v5_cmd_dates_intro', 0)) . '</p>';
$strStamp = fxBibTexte('bib_v5_cmd_realite_stamp', 0);
foreach (fxBibCommandeMilestoneKinds() as $strKind) {
$tabRow = $tabMilestones[$strKind] ?? ['due' => null, 'done_at' => null];
$tabRow = $tabMilestones[$strKind] ?? [
'due' => null,
'done_at' => null,
'done_by' => '',
'has_log' => false,
];
$strDue = (string)($tabRow['due'] ?? '');
$strDone = fxBibCommandeFormatDoneAt($tabRow['done_at'] ?? '');
$strBy = trim((string)($tabRow['done_by'] ?? ''));
if (($tabRow['done_at'] ?? null) === null) {
$strBy = '';
}
$blnHasLog = !empty($tabRow['has_log']);
$html .= '<div class="bib-cmd-jalon" data-kind="' . fxBibEsc($strKind) . '">';
$html .= '<div><strong>' . fxBibEsc(fxBibTexte('bib_v5_cmd_ms_' . $strKind, 0)) . '</strong>'
. '<div class="text-muted small">' . fxBibEsc(fxBibTexte('bib_v5_cmd_ms_' . $strKind . '_help', 0)) . '</div></div>';
@ -534,8 +724,20 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) {
. fxBibEsc(fxBibTexte('bib_v5_cmd_jalon', 0)) . '</span>'
. '<input type="date" class="form-control form-control-sm bib-cmd-due" value="'
. fxBibEsc($strDue) . '"></label>';
$html .= '<div><div class="text-muted small">' . fxBibEsc(fxBibTexte('bib_v5_cmd_realite', 0))
. '</div><div class="bib-cmd-realite">' . fxBibEsc($strDone) . '</div></div>';
$html .= '<div class="bib-cmd-realite-box">';
$html .= '<div class="text-muted small">' . fxBibEsc(fxBibTexte('bib_v5_cmd_realite', 0)) . '</div>';
$html .= '<div class="bib-cmd-realite">';
$html .= '<span class="bib-cmd-realite-when">' . fxBibEsc($strDone) . '</span>';
if ($blnHasLog) {
$html .= fxBibCommandeRenderRealiteBang($strKind);
}
$html .= '</div>';
$html .= '<div class="bib-cmd-realite-by"'
. ($strBy === '' ? ' hidden' : '') . '>'
. fxBibEsc($strBy) . '</div>';
$html .= '<button type="button" class="btn btn-outline-secondary btn-sm bib-cmd-realite-stamp">'
. fxBibEsc($strStamp) . '</button>';
$html .= '</div>';
$html .= '</div>';
}
@ -974,7 +1176,16 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) {
data-locked="<?php echo $blnLocked ? '1' : '0'; ?>"
data-label-saved="<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_saved', 0)); ?>"
data-label-apply-done="<?php echo fxBibEsc(fxBibTexte('bib_v5_production_fields_apply_all_done', 0)); ?>"
data-label-remove="<?php echo fxBibEsc(fxBibTexte('bib_v5_production_fields_remove', 0)); ?>">
data-label-remove="<?php echo fxBibEsc(fxBibTexte('bib_v5_production_fields_remove', 0)); ?>"
data-audit-title="<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_realite_hist_title', 0)); ?>"
data-audit-empty="<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_realite_hist_empty', 0)); ?>"
data-audit-close="<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_realite_hist_close', 0)); ?>"
data-audit-when="<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_realite_hist_when', 0)); ?>"
data-audit-who="<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_realite_hist_who', 0)); ?>"
data-audit-from="<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_realite_hist_from', 0)); ?>"
data-audit-to="<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_realite_hist_to', 0)); ?>"
data-bang-title="<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_realite_bang_title', 0)); ?>"
data-bang-aria="<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_realite_bang_aria', 0)); ?>">
<div class="bib-cmd-pagebar">
<a class="btn btn-outline-secondary btn-sm" href="<?php echo fxBibEsc(fxBibCommandeBackUrl($intEveId, $strLangue)); ?>">
<?php echo fxBibEsc(fxBibTexte('bib_v5_cmd_back', 0)); ?>
@ -1002,6 +1213,13 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) {
</div>
</div>
</div>
<div id="bib-cmd-audit-dialog" class="bib-cmd-audit-dialog" hidden>
<div class="bib-cmd-audit-dialog-card" role="dialog" aria-modal="true" aria-labelledby="bib-cmd-audit-dialog-title">
<h2 id="bib-cmd-audit-dialog-title"></h2>
<div class="bib-cmd-audit-dialog-body"></div>
<button type="button" class="btn btn-secondary btn-sm bib-cmd-audit-dialog-close"></button>
</div>
</div>
<?php
return ob_get_clean();
}

View File

@ -2930,10 +2930,7 @@ function fxBibProductionLockOfficialOrder($intEveId, $strKind = 'dossards') {
if ($strKind === 'dossards' && function_exists('fxBibCommandeSetOrderRangesLocked')) {
fxBibCommandeSetOrderRangesLocked($intOrderId, true);
}
if (function_exists('fxBibCommandeMarkMilestoneDone')
&& function_exists('fxBibCommandeMilestoneKindForFile')) {
fxBibCommandeMarkMilestoneDone($intEveId, fxBibCommandeMilestoneKindForFile($strKind));
}
// MSIN-4579 — Réalité jalon : manuelle seulement (bouton), plus dauto au blocage.
return ['success' => true, 'order' => fxBibProductionGetOrder($intOrderId, $intEveId)];
}

View File

@ -4056,10 +4056,20 @@ function fxBibStaticFallback($clef) {
'bib_v5_cmd_page_title' => ['fr' => 'Commande dossards / puces', 'en' => 'Bib / chip order'],
'bib_v5_cmd_back' => ['fr' => 'Retour aux dossards', 'en' => 'Back to bibs'],
'bib_v5_cmd_dates_title' => ['fr' => 'Dates importantes', 'en' => 'Important dates'],
'bib_v5_cmd_dates_intro' => ['fr' => 'Toutes des jalons (échéances). La réalité se remplira plus tard, quand laction aura eu lieu.', 'en' => 'These are all deadlines. Actual dates fill in later, when the action happens.'],
'bib_v5_cmd_dates_intro' => ['fr' => 'Toutes des jalons (échéances). Pour linstant la réalité nest pas automatique (génération Excel, etc.) : cliquez le bouton pour poser la date et lheure du jour, avec le nom de la personne. Lhistorique se consulte avec le « ! », comme les champs de changement.', 'en' => 'These are all deadlines. For now the actual date is not automatic (Excel generation, etc.): click the button to stamp todays date and time, with the persons name. History is the « ! », same as change fields.'],
'bib_v5_cmd_jalon' => ['fr' => 'Jalon', 'en' => 'Deadline'],
'bib_v5_cmd_realite' => ['fr' => 'Réalité', 'en' => 'Actual'],
'bib_v5_cmd_realite_empty' => ['fr' => '—', 'en' => '—'],
'bib_v5_cmd_realite_stamp' => ['fr' => 'Marquer réalisé aujourdhui', 'en' => 'Mark done today'],
'bib_v5_cmd_realite_bang_title' => ['fr' => 'Voir les derniers changements', 'en' => 'View recent changes'],
'bib_v5_cmd_realite_bang_aria' => ['fr' => 'Historique des modifications', 'en' => 'Change history'],
'bib_v5_cmd_realite_hist_title' => ['fr' => 'Derniers changements', 'en' => 'Recent changes'],
'bib_v5_cmd_realite_hist_empty' => ['fr' => 'Aucun historique pour ce jalon.', 'en' => 'No history for this milestone.'],
'bib_v5_cmd_realite_hist_close' => ['fr' => 'Fermer', 'en' => 'Close'],
'bib_v5_cmd_realite_hist_when' => ['fr' => 'Quand', 'en' => 'When'],
'bib_v5_cmd_realite_hist_who' => ['fr' => 'Par', 'en' => 'By'],
'bib_v5_cmd_realite_hist_from' => ['fr' => 'Avant', 'en' => 'From'],
'bib_v5_cmd_realite_hist_to' => ['fr' => 'Après', 'en' => 'To'],
'bib_v5_cmd_ms_client_wanted' => ['fr' => 'Le client les veut', 'en' => 'Client wants them by'],
'bib_v5_cmd_ms_client_wanted_help' => ['fr' => 'Les dossards chez le client pour cette date', 'en' => 'Bibs with the client by this date'],
'bib_v5_cmd_ms_visual_approved' => ['fr' => 'Visuel approuvé', 'en' => 'Visual approved'],

View File

@ -7,7 +7,7 @@
* Constantes *
*
**************/
define('_VERSION_CODE', '4.73.040'); // MSIN-4579 — note puces + note dossards par séquence
define('_VERSION_CODE', '4.73.041'); // MSIN-4579 — réalité jalon manuelle (bouton + historique)
define('_DATE_CODE', '2026-08-20');
//MSIN-4290
define('QR_SECRET_KEY', 'ms1_qr_2026_cle_secrete_longue_et_fixe');

View File

@ -0,0 +1,79 @@
-- MSIN-4579 — Réalité jalon manuelle (bouton + historique)
-- Parent : MSIN-4511 / MSIN-4577. Prérequis : sql/MSIN-4577-commande-dossards-puces.sql
-- Notes : exécution UNIQUEMENT sur dev préprod ; autres env = Navicat structure + sync_static_db
SET @db := DATABASE();
-- Qui a posé la réalité (distinct de bcm_updated_by, qui change aussi avec le jalon)
SELECT COUNT(*) INTO @col_done_by
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = @db
AND TABLE_NAME = 'inscriptions_bib_commande_milestones'
AND COLUMN_NAME = 'bcm_done_by';
SET @sql := IF(
@col_done_by = 0,
'ALTER TABLE inscriptions_bib_commande_milestones
ADD COLUMN bcm_done_by VARCHAR(128) NOT NULL DEFAULT ''''
COMMENT ''MSIN-4579 — qui a posé bcm_done_at'' AFTER bcm_done_at',
'SELECT ''bcm_done_by already exists'' AS note'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
CREATE TABLE IF NOT EXISTS `inscriptions_bib_commande_milestone_log` (
`bcml_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`eve_id` INT NOT NULL,
`bcm_kind` VARCHAR(32) NOT NULL,
`old_value` VARCHAR(32) NOT NULL DEFAULT '',
`new_value` VARCHAR(32) NOT NULL DEFAULT '',
`changed_by_label` VARCHAR(128) NOT NULL DEFAULT '',
`created_at` DATETIME NOT NULL,
PRIMARY KEY (`bcml_id`),
KEY `idx_bcml_eve_kind` (`eve_id`, `bcm_kind`, `created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
COMMENT='MSIN-4579 — historique réalité jalon (comme champs de changement)';
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 src.info_clef, src.info_langue, src.info_texte, src.info_aide, 'compte.php',
'MSIN-4579', 0, 1, '', '', '', NOW()
FROM (
SELECT 'bib_v5_cmd_realite_stamp' info_clef, 'fr' info_langue,
'Marquer réalisé aujourdhui' info_texte, '' info_aide
UNION ALL SELECT 'bib_v5_cmd_realite_stamp', 'en', 'Mark done today', ''
UNION ALL SELECT 'bib_v5_cmd_realite_bang_title', 'fr', 'Voir les derniers changements', ''
UNION ALL SELECT 'bib_v5_cmd_realite_bang_title', 'en', 'View recent changes', ''
UNION ALL SELECT 'bib_v5_cmd_realite_bang_aria', 'fr', 'Historique des modifications', ''
UNION ALL SELECT 'bib_v5_cmd_realite_bang_aria', 'en', 'Change history', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_title', 'fr', 'Derniers changements', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_title', 'en', 'Recent changes', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_empty', 'fr', 'Aucun historique pour ce jalon.', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_empty', 'en', 'No history for this milestone.', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_close', 'fr', 'Fermer', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_close', 'en', 'Close', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_when', 'fr', 'Quand', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_when', 'en', 'When', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_who', 'fr', 'Par', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_who', 'en', 'By', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_from', 'fr', 'Avant', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_from', 'en', 'From', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_to', 'fr', 'Après', ''
UNION ALL SELECT 'bib_v5_cmd_realite_hist_to', 'en', 'To', ''
) src
WHERE NOT EXISTS (
SELECT 1 FROM info current_info
WHERE current_info.info_clef = src.info_clef
AND current_info.info_langue = src.info_langue
AND current_info.info_prg = 'compte.php'
);
UPDATE info
SET info_texte = 'Toutes des jalons (échéances). Pour linstant la réalité nest pas automatique (génération Excel, etc.) : cliquez le bouton pour poser la date et lheure du jour, avec le nom de la personne. Lhistorique se consulte avec le « ! », comme les champs de changement.'
WHERE info_clef = 'bib_v5_cmd_dates_intro' AND info_langue = 'fr' AND info_prg = 'compte.php';
UPDATE info
SET info_texte = 'These are all deadlines. For now the actual date is not automatic (Excel generation, etc.): click the button to stamp todays date and time, with the persons name. History is the « ! », same as change fields.'
WHERE info_clef = 'bib_v5_cmd_dates_intro' AND info_langue = 'en' AND info_prg = 'compte.php';