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:
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -51,7 +51,139 @@
|
||||
}).then(function (res) { return res.json(); });
|
||||
}
|
||||
|
||||
function cmdEsc(str) {
|
||||
return String(str == null ? '' : str)
|
||||
.replace(/&/g, '&')
|
||||
.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 = '<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();
|
||||
}());
|
||||
|
||||
@ -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 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<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();
|
||||
}
|
||||
|
||||
@ -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 d’auto au blocage.
|
||||
|
||||
return ['success' => true, 'order' => fxBibProductionGetOrder($intOrderId, $intEveId)];
|
||||
}
|
||||
|
||||
@ -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'],
|
||||
|
||||
@ -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');
|
||||
|
||||
79
sql/MSIN-4579-jalon-realite-manuelle.sql
Normal file
79
sql/MSIN-4579-jalon-realite-manuelle.sql
Normal 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é 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';
|
||||
Reference in New Issue
Block a user