fix(ui): show Reimporta button on DELETED_IN_ROA orders
mark_order_deleted_in_roa wipes order_items, so renderOrderDetailModal hit the items.length===0 early-return BEFORE configuring footer buttons — leaving DELETED_IN_ROA orders with no way to retry from the UI. Extract the 3 button configurators (Retry/Resync/Delete) into _configureDetailButtons() and call it before the early-return. Also fire onAfterRender on the empty-items path for consistency. Cache-bust shared.js v47 → v48. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -601,6 +601,143 @@ function _renderReceipt(items, order) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Order Detail Modal (shared) ──────────────────
|
// ── Order Detail Modal (shared) ──────────────────
|
||||||
|
/**
|
||||||
|
function _configureDetailButtons(order, orderNumber, opts) {
|
||||||
|
const status = (order.status || '').toUpperCase();
|
||||||
|
const isInvoiced = !!(order.factura_numar);
|
||||||
|
|
||||||
|
const retryBtn = document.getElementById('detailRetryBtn');
|
||||||
|
if (retryBtn) {
|
||||||
|
const canRetry = [ORDER_STATUS.ERROR, ORDER_STATUS.SKIPPED, ORDER_STATUS.DELETED_IN_ROA].includes(status);
|
||||||
|
retryBtn.style.display = canRetry ? '' : 'none';
|
||||||
|
if (canRetry) {
|
||||||
|
retryBtn.onclick = async () => {
|
||||||
|
retryBtn.disabled = true;
|
||||||
|
retryBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Reimportare...';
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/retry`, { method: 'POST' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
retryBtn.innerHTML = '<i class="bi bi-check-circle"></i> ' + (data.message || 'Reimportat');
|
||||||
|
retryBtn.className = 'btn btn-sm btn-success';
|
||||||
|
if (opts.onStatusChange) opts.onStatusChange();
|
||||||
|
setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500);
|
||||||
|
} else {
|
||||||
|
retryBtn.innerHTML = '<i class="bi bi-exclamation-triangle"></i> ' + (data.message || 'Eroare');
|
||||||
|
retryBtn.className = 'btn btn-sm btn-danger';
|
||||||
|
setTimeout(() => {
|
||||||
|
retryBtn.innerHTML = '<i class="bi bi-arrow-clockwise"></i> Reimporta';
|
||||||
|
retryBtn.className = 'btn btn-sm btn-outline-primary';
|
||||||
|
retryBtn.disabled = false;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
retryBtn.innerHTML = 'Eroare: ' + err.message;
|
||||||
|
retryBtn.disabled = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resyncBtn = document.getElementById('detailResyncBtn');
|
||||||
|
if (resyncBtn) {
|
||||||
|
const canResync = [ORDER_STATUS.IMPORTED, ORDER_STATUS.ALREADY_IMPORTED].includes(status);
|
||||||
|
resyncBtn.style.display = canResync ? '' : 'none';
|
||||||
|
if (canResync) {
|
||||||
|
if (isInvoiced) {
|
||||||
|
resyncBtn.disabled = true;
|
||||||
|
resyncBtn.style.opacity = '0.5';
|
||||||
|
resyncBtn.style.pointerEvents = 'none';
|
||||||
|
resyncBtn.title = 'Comanda facturata';
|
||||||
|
} else {
|
||||||
|
resyncBtn.disabled = false;
|
||||||
|
resyncBtn.style.opacity = '';
|
||||||
|
resyncBtn.style.pointerEvents = '';
|
||||||
|
resyncBtn.title = '';
|
||||||
|
resyncBtn.onclick = () => {
|
||||||
|
inlineConfirmAction(resyncBtn, 'Confirmi resync?', async (btn) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/resync`, { method: 'POST' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
btn.innerHTML = '<i class="bi bi-check-circle"></i> Reimportat';
|
||||||
|
btn.className = 'btn btn-sm btn-success';
|
||||||
|
if (opts.onStatusChange) opts.onStatusChange();
|
||||||
|
setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500);
|
||||||
|
} else {
|
||||||
|
btn.innerHTML = '<i class="bi bi-exclamation-triangle"></i> ' + (data.message || 'Eroare');
|
||||||
|
btn.className = 'btn btn-sm btn-danger';
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.innerHTML = '<i class="bi bi-arrow-repeat"></i> Resync';
|
||||||
|
btn.className = 'btn btn-sm btn-outline-warning';
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
btn.innerHTML = 'Eroare: ' + err.message;
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
defaultHtml: '<i class="bi bi-arrow-repeat"></i> Resync',
|
||||||
|
loadingText: 'Resync...',
|
||||||
|
confirmClass: 'btn-warning',
|
||||||
|
defaultBtnClass: 'btn-outline-warning'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteBtn = document.getElementById('detailDeleteBtn');
|
||||||
|
if (deleteBtn) {
|
||||||
|
const canDelete = [ORDER_STATUS.IMPORTED, ORDER_STATUS.ALREADY_IMPORTED].includes(status);
|
||||||
|
deleteBtn.style.display = canDelete ? '' : 'none';
|
||||||
|
if (canDelete) {
|
||||||
|
if (isInvoiced) {
|
||||||
|
deleteBtn.disabled = true;
|
||||||
|
deleteBtn.style.opacity = '0.5';
|
||||||
|
deleteBtn.style.pointerEvents = 'none';
|
||||||
|
deleteBtn.title = 'Comanda facturata';
|
||||||
|
} else {
|
||||||
|
deleteBtn.disabled = false;
|
||||||
|
deleteBtn.style.opacity = '';
|
||||||
|
deleteBtn.style.pointerEvents = '';
|
||||||
|
deleteBtn.title = '';
|
||||||
|
deleteBtn.onclick = () => {
|
||||||
|
inlineConfirmAction(deleteBtn, 'Confirmi stergerea?', async (btn) => {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/delete`, { method: 'POST' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
btn.innerHTML = '<i class="bi bi-check-circle"></i> Sters';
|
||||||
|
btn.className = 'btn btn-sm btn-danger';
|
||||||
|
if (opts.onStatusChange) opts.onStatusChange();
|
||||||
|
setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500);
|
||||||
|
} else {
|
||||||
|
btn.innerHTML = '<i class="bi bi-exclamation-triangle"></i> ' + (data.message || 'Eroare');
|
||||||
|
btn.className = 'btn btn-sm btn-danger';
|
||||||
|
setTimeout(() => {
|
||||||
|
btn.innerHTML = '<i class="bi bi-trash"></i> Sterge din ROA';
|
||||||
|
btn.className = 'btn btn-sm btn-outline-danger';
|
||||||
|
btn.disabled = false;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
btn.innerHTML = 'Eroare: ' + err.message;
|
||||||
|
btn.disabled = false;
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
defaultHtml: '<i class="bi bi-trash"></i> Sterge din ROA',
|
||||||
|
loadingText: 'Stergere...',
|
||||||
|
confirmClass: 'btn-danger',
|
||||||
|
defaultBtnClass: 'btn-outline-danger'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render and show the order detail modal.
|
* Render and show the order detail modal.
|
||||||
* @param {string} orderNumber
|
* @param {string} orderNumber
|
||||||
@@ -717,9 +854,14 @@ async function renderOrderDetailModal(orderNumber, opts) {
|
|||||||
document.getElementById('detailError').style.display = '';
|
document.getElementById('detailError').style.display = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure footer action buttons BEFORE any early-return on items —
|
||||||
|
// DELETED_IN_ROA orders have no items but must still expose the Reimporta button.
|
||||||
|
_configureDetailButtons(order, orderNumber, opts);
|
||||||
|
|
||||||
const items = data.items || [];
|
const items = data.items || [];
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="9" class="text-center text-muted">Niciun articol</td></tr>';
|
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="9" class="text-center text-muted">Niciun articol</td></tr>';
|
||||||
|
if (opts.onAfterRender) opts.onAfterRender(order, items);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -853,143 +995,6 @@ async function renderOrderDetailModal(orderNumber, opts) {
|
|||||||
document.getElementById('detailItemsBody').innerHTML = tableHtml;
|
document.getElementById('detailItemsBody').innerHTML = tableHtml;
|
||||||
_renderReceipt(items, order);
|
_renderReceipt(items, order);
|
||||||
|
|
||||||
// Retry button (only for ERROR/SKIPPED orders)
|
|
||||||
const retryBtn = document.getElementById('detailRetryBtn');
|
|
||||||
if (retryBtn) {
|
|
||||||
const canRetry = [ORDER_STATUS.ERROR, ORDER_STATUS.SKIPPED, ORDER_STATUS.DELETED_IN_ROA].includes((order.status || '').toUpperCase());
|
|
||||||
retryBtn.style.display = canRetry ? '' : 'none';
|
|
||||||
if (canRetry) {
|
|
||||||
retryBtn.onclick = async () => {
|
|
||||||
retryBtn.disabled = true;
|
|
||||||
retryBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Reimportare...';
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/retry`, { method: 'POST' });
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.success) {
|
|
||||||
retryBtn.innerHTML = '<i class="bi bi-check-circle"></i> ' + (data.message || 'Reimportat');
|
|
||||||
retryBtn.className = 'btn btn-sm btn-success';
|
|
||||||
if (opts.onStatusChange) opts.onStatusChange();
|
|
||||||
// Refresh modal after short delay
|
|
||||||
setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500);
|
|
||||||
} else {
|
|
||||||
retryBtn.innerHTML = '<i class="bi bi-exclamation-triangle"></i> ' + (data.message || 'Eroare');
|
|
||||||
retryBtn.className = 'btn btn-sm btn-danger';
|
|
||||||
setTimeout(() => {
|
|
||||||
retryBtn.innerHTML = '<i class="bi bi-arrow-clockwise"></i> Reimporta';
|
|
||||||
retryBtn.className = 'btn btn-sm btn-outline-primary';
|
|
||||||
retryBtn.disabled = false;
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
retryBtn.innerHTML = 'Eroare: ' + err.message;
|
|
||||||
retryBtn.disabled = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resync button (IMPORTED/ALREADY_IMPORTED only)
|
|
||||||
const resyncBtn = document.getElementById('detailResyncBtn');
|
|
||||||
if (resyncBtn) {
|
|
||||||
const canResync = [ORDER_STATUS.IMPORTED, ORDER_STATUS.ALREADY_IMPORTED].includes((order.status || '').toUpperCase());
|
|
||||||
resyncBtn.style.display = canResync ? '' : 'none';
|
|
||||||
if (canResync) {
|
|
||||||
const isInvoiced = !!(order.factura_numar);
|
|
||||||
if (isInvoiced) {
|
|
||||||
resyncBtn.disabled = true;
|
|
||||||
resyncBtn.style.opacity = '0.5';
|
|
||||||
resyncBtn.style.pointerEvents = 'none';
|
|
||||||
resyncBtn.title = 'Comanda facturata';
|
|
||||||
} else {
|
|
||||||
resyncBtn.disabled = false;
|
|
||||||
resyncBtn.style.opacity = '';
|
|
||||||
resyncBtn.style.pointerEvents = '';
|
|
||||||
resyncBtn.title = '';
|
|
||||||
resyncBtn.onclick = () => {
|
|
||||||
inlineConfirmAction(resyncBtn, 'Confirmi resync?', async (btn) => {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/resync`, { method: 'POST' });
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.success) {
|
|
||||||
btn.innerHTML = '<i class="bi bi-check-circle"></i> Reimportat';
|
|
||||||
btn.className = 'btn btn-sm btn-success';
|
|
||||||
if (opts.onStatusChange) opts.onStatusChange();
|
|
||||||
setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500);
|
|
||||||
} else {
|
|
||||||
btn.innerHTML = '<i class="bi bi-exclamation-triangle"></i> ' + (data.message || 'Eroare');
|
|
||||||
btn.className = 'btn btn-sm btn-danger';
|
|
||||||
setTimeout(() => {
|
|
||||||
btn.innerHTML = '<i class="bi bi-arrow-repeat"></i> Resync';
|
|
||||||
btn.className = 'btn btn-sm btn-outline-warning';
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
btn.innerHTML = 'Eroare: ' + err.message;
|
|
||||||
btn.disabled = false;
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
defaultHtml: '<i class="bi bi-arrow-repeat"></i> Resync',
|
|
||||||
loadingText: 'Resync...',
|
|
||||||
confirmClass: 'btn-warning',
|
|
||||||
defaultBtnClass: 'btn-outline-warning'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete button (IMPORTED/ALREADY_IMPORTED only)
|
|
||||||
const deleteBtn = document.getElementById('detailDeleteBtn');
|
|
||||||
if (deleteBtn) {
|
|
||||||
const canDelete = [ORDER_STATUS.IMPORTED, ORDER_STATUS.ALREADY_IMPORTED].includes((order.status || '').toUpperCase());
|
|
||||||
deleteBtn.style.display = canDelete ? '' : 'none';
|
|
||||||
if (canDelete) {
|
|
||||||
const isInvoiced = !!(order.factura_numar);
|
|
||||||
if (isInvoiced) {
|
|
||||||
deleteBtn.disabled = true;
|
|
||||||
deleteBtn.style.opacity = '0.5';
|
|
||||||
deleteBtn.style.pointerEvents = 'none';
|
|
||||||
deleteBtn.title = 'Comanda facturata';
|
|
||||||
} else {
|
|
||||||
deleteBtn.disabled = false;
|
|
||||||
deleteBtn.style.opacity = '';
|
|
||||||
deleteBtn.style.pointerEvents = '';
|
|
||||||
deleteBtn.title = '';
|
|
||||||
deleteBtn.onclick = () => {
|
|
||||||
inlineConfirmAction(deleteBtn, 'Confirmi stergerea?', async (btn) => {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/delete`, { method: 'POST' });
|
|
||||||
const data = await res.json();
|
|
||||||
if (data.success) {
|
|
||||||
btn.innerHTML = '<i class="bi bi-check-circle"></i> Sters';
|
|
||||||
btn.className = 'btn btn-sm btn-danger';
|
|
||||||
if (opts.onStatusChange) opts.onStatusChange();
|
|
||||||
setTimeout(() => renderOrderDetailModal(orderNumber, opts), 1500);
|
|
||||||
} else {
|
|
||||||
btn.innerHTML = '<i class="bi bi-exclamation-triangle"></i> ' + (data.message || 'Eroare');
|
|
||||||
btn.className = 'btn btn-sm btn-danger';
|
|
||||||
setTimeout(() => {
|
|
||||||
btn.innerHTML = '<i class="bi bi-trash"></i> Sterge din ROA';
|
|
||||||
btn.className = 'btn btn-sm btn-outline-danger';
|
|
||||||
btn.disabled = false;
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
btn.innerHTML = 'Eroare: ' + err.message;
|
|
||||||
btn.disabled = false;
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
defaultHtml: '<i class="bi bi-trash"></i> Sterge din ROA',
|
|
||||||
loadingText: 'Stergere...',
|
|
||||||
confirmClass: 'btn-danger',
|
|
||||||
defaultBtnClass: 'btn-outline-danger'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.onAfterRender) opts.onAfterRender(order, items);
|
if (opts.onAfterRender) opts.onAfterRender(order, items);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
document.getElementById('detailError').textContent = err.message;
|
document.getElementById('detailError').textContent = err.message;
|
||||||
|
|||||||
@@ -169,7 +169,7 @@
|
|||||||
|
|
||||||
<script>window.ROOT_PATH = "{{ rp }}";</script>
|
<script>window.ROOT_PATH = "{{ rp }}";</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="{{ rp }}/static/js/shared.js?v=47"></script>
|
<script src="{{ rp }}/static/js/shared.js?v=48"></script>
|
||||||
<script>
|
<script>
|
||||||
// Dark mode toggle
|
// Dark mode toggle
|
||||||
function toggleDarkMode() {
|
function toggleDarkMode() {
|
||||||
|
|||||||
Reference in New Issue
Block a user