From 97701b5fa304ab2109ec3bbefcbf3b18e184eb7c Mon Sep 17 00:00:00 2001 From: stephan Date: Thu, 20 Aug 2026 13:02:32 -0400 Subject: [PATCH] =?UTF-8?q?MSIN-4579=20=E2=80=94=20Implemented=20manual=20?= =?UTF-8?q?milestone=20management=20features,=20including=20the=20ability?= =?UTF-8?q?=20to=20mark=20milestones=20as=20done=20and=20view=20their=20hi?= =?UTF-8?q?story.=20Updated=20PHP=20functions=20to=20handle=20new=20action?= =?UTF-8?q?s=20and=20added=20corresponding=20AJAX=20endpoints.=20Enhanced?= =?UTF-8?q?=20the=20UI=20with=20new=20CSS=20styles=20for=20better=20usabil?= =?UTF-8?q?ity=20and=20added=20localization=20for=20new=20dialog=20element?= =?UTF-8?q?s.=20Incremented=20version=20to=204.73.041=20to=20reflect=20the?= =?UTF-8?q?se=20changes.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ajax_bib_range.php | 49 +++++ css/style.css | 46 +++- js/v2/bib-commande.js | 146 +++++++++++++ php/inc_fx_bib_commande.php | 264 +++++++++++++++++++++-- php/inc_fx_bib_production.php | 5 +- php/inc_fx_promoteur.php | 12 +- php/inc_settings.php | 2 +- sql/MSIN-4579-jalon-realite-manuelle.sql | 79 +++++++ 8 files changed, 573 insertions(+), 30 deletions(-) create mode 100644 sql/MSIN-4579-jalon-realite-manuelle.sql diff --git a/ajax_bib_range.php b/ajax_bib_range.php index 99111f82..beb1375c 100644 --- a/ajax_bib_range.php +++ b/ajax_bib_range.php @@ -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); diff --git a/css/style.css b/css/style.css index 9bdc0987..d734cdff 100644 --- a/css/style.css +++ b/css/style.css @@ -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; diff --git a/js/v2/bib-commande.js b/js/v2/bib-commande.js index cd272b6d..f13e1bfb 100644 --- a/js/v2/bib-commande.js +++ b/js/v2/bib-commande.js @@ -51,7 +51,139 @@ }).then(function (res) { return res.json(); }); } + function cmdEsc(str) { + return String(str == null ? '' : str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + } + + 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 = '

' + cmdEsc(page.getAttribute('data-audit-empty') || '') + '

'; + } else { + var html = '
'; + html += '' + + '' + + '' + + '' + + '' + + ''; + for (var i = 0; i < items.length; i++) { + var row = items[i] || {}; + html += '' + + '' + + '' + + '' + + '' + + ''; + } + html += '
' + cmdEsc(page.getAttribute('data-audit-when') || '') + '' + cmdEsc(page.getAttribute('data-audit-who') || '') + '' + cmdEsc(page.getAttribute('data-audit-from') || '') + '' + cmdEsc(page.getAttribute('data-audit-to') || '') + '
' + cmdEsc(row.when || '') + '' + cmdEsc(row.who || '') + '' + cmdEsc(row.old || '') + '' + cmdEsc(row.new || '') + '
'; + 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(); }()); diff --git a/php/inc_fx_bib_commande.php b/php/inc_fx_bib_commande.php index e4a7b6b3..84917197 100644 --- a/php/inc_fx_bib_commande.php +++ b/php/inc_fx_bib_commande.php @@ -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 + * @return array */ 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 d’auto + * à 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é d’un jalon (même colonnes que les champs de changement). + * @return array + */ + 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 ''; } + function fxBibCommandeRenderRealiteBang($strKind) { + return ' '; + } + function fxBibCommandeRenderDatesBox(array $tabMilestones) { $html = fxBibCommandeRenderFoldOpen( '1 ' . fxBibEsc(fxBibTexte('bib_v5_cmd_dates_title', 0)) ); $html .= '

' . fxBibEsc(fxBibTexte('bib_v5_cmd_dates_intro', 0)) . '

'; + $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 .= '
'; $html .= '
' . fxBibEsc(fxBibTexte('bib_v5_cmd_ms_' . $strKind, 0)) . '' . '
' . fxBibEsc(fxBibTexte('bib_v5_cmd_ms_' . $strKind . '_help', 0)) . '
'; @@ -534,8 +724,20 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) { . fxBibEsc(fxBibTexte('bib_v5_cmd_jalon', 0)) . '' . ''; - $html .= '
' . fxBibEsc(fxBibTexte('bib_v5_cmd_realite', 0)) - . '
' . fxBibEsc($strDone) . '
'; + $html .= '
'; + $html .= '
' . fxBibEsc(fxBibTexte('bib_v5_cmd_realite', 0)) . '
'; + $html .= '
'; + $html .= '' . fxBibEsc($strDone) . ''; + if ($blnHasLog) { + $html .= fxBibCommandeRenderRealiteBang($strKind); + } + $html .= '
'; + $html .= '
' + . fxBibEsc($strBy) . '
'; + $html .= ''; + $html .= '
'; $html .= '
'; } @@ -974,7 +1176,16 @@ if (!function_exists('fxBibCommandeMilestoneKinds')) { data-locked="" data-label-saved="" data-label-apply-done="" - data-label-remove=""> + data-label-remove="" + data-audit-title="" + data-audit-empty="" + data-audit-close="" + data-audit-when="" + data-audit-who="" + data-audit-from="" + data-audit-to="" + data-bang-title="" + data-bang-aria=""> + true, 'order' => fxBibProductionGetOrder($intOrderId, $intEveId)]; } diff --git a/php/inc_fx_promoteur.php b/php/inc_fx_promoteur.php index 2dfee2f8..217bb11d 100644 --- a/php/inc_fx_promoteur.php +++ b/php/inc_fx_promoteur.php @@ -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 l’action 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 l’instant la réalité n’est pas automatique (génération Excel, etc.) : cliquez le bouton pour poser la date et l’heure du jour, avec le nom de la personne. L’historique 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 today’s date and time, with the person’s 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é aujourd’hui', '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'], diff --git a/php/inc_settings.php b/php/inc_settings.php index f371ae01..c8e0e48c 100644 --- a/php/inc_settings.php +++ b/php/inc_settings.php @@ -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'); diff --git a/sql/MSIN-4579-jalon-realite-manuelle.sql b/sql/MSIN-4579-jalon-realite-manuelle.sql new file mode 100644 index 00000000..bc41d66a --- /dev/null +++ b/sql/MSIN-4579-jalon-realite-manuelle.sql @@ -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é aujourd’hui' 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 l’instant la réalité n’est pas automatique (génération Excel, etc.) : cliquez le bouton pour poser la date et l’heure du jour, avec le nom de la personne. L’historique 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 today’s date and time, with the person’s 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';