feat(orders): add resync and delete order buttons

Resync soft-deletes from Oracle then re-imports from GoMag with fresh
article data. Delete soft-deletes and marks DELETED_IN_ROA. Both have
invoice safety gates (refuse if invoiced or Oracle unavailable).

UI: split modal footer (Delete left, Resync+Close right), inline
confirm pattern (no native confirm()), dashboard row hover action
icons, disabled+tooltip for invoiced orders. 8 unit tests for safety
gates and happy paths.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-04-09 13:10:01 +00:00
parent 90a4906d87
commit 25f73db64d
8 changed files with 621 additions and 34 deletions

View File

@@ -375,7 +375,7 @@ async function loadDashOrders() {
<td>${o.items_count || 0}</td>
<td class="text-end text-muted">${fmtCost(o.delivery_cost)}</td>
<td class="text-end text-muted">${fmtCost(o.discount_total)}</td>
<td class="text-end fw-bold">${orderTotal}</td>
<td class="text-end fw-bold" style="position:relative">${orderTotal}${(o.status === 'IMPORTED' || o.status === 'ALREADY_IMPORTED') && !(o.invoice && o.invoice.facturat) ? '<span class="row-actions"><button class="btn btn-xs btn-outline-warning" aria-label="Resync comanda" title="Resync" onclick="event.stopPropagation(); dashResyncOrder(\'' + esc(o.order_number) + '\', this)"><i class="bi bi-arrow-repeat"></i></button><button class="btn btn-xs btn-outline-danger" aria-label="Sterge din ROA" title="Sterge din ROA" onclick="event.stopPropagation(); dashDeleteOrder(\'' + esc(o.order_number) + '\', this)"><i class="bi bi-trash"></i></button></span>' : ''}</td>
</tr>`;
}).join('');
}
@@ -595,3 +595,69 @@ function openDashQuickMap(sku, productName, orderNumber, itemIdx) {
});
}
// ── Dashboard row action handlers ────────────────
function dashResyncOrder(orderNumber, btn) {
inlineConfirmAction(btn, '?', async (b) => {
try {
const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/resync`, { method: 'POST' });
const data = await res.json();
if (data.success) {
b.innerHTML = '<i class="bi bi-check-circle"></i>';
b.className = 'btn btn-xs btn-success';
setTimeout(() => loadDashOrders(), 1500);
} else {
b.innerHTML = '<i class="bi bi-exclamation-triangle"></i>';
b.className = 'btn btn-xs btn-danger';
b.title = data.message || 'Eroare';
setTimeout(() => {
b.innerHTML = '<i class="bi bi-arrow-repeat"></i>';
b.className = 'btn btn-xs btn-outline-warning';
b.disabled = false;
b.title = 'Resync';
}, 3000);
}
} catch (err) {
b.innerHTML = '<i class="bi bi-exclamation-triangle"></i>';
b.disabled = false;
}
}, {
defaultHtml: '<i class="bi bi-arrow-repeat"></i>',
loadingText: '',
confirmClass: 'btn-warning',
defaultBtnClass: 'btn-outline-warning'
});
}
function dashDeleteOrder(orderNumber, btn) {
inlineConfirmAction(btn, '?', async (b) => {
try {
const res = await fetch(`/api/orders/${encodeURIComponent(orderNumber)}/delete`, { method: 'POST' });
const data = await res.json();
if (data.success) {
b.innerHTML = '<i class="bi bi-check-circle"></i>';
b.className = 'btn btn-xs btn-success';
setTimeout(() => loadDashOrders(), 1500);
} else {
b.innerHTML = '<i class="bi bi-exclamation-triangle"></i>';
b.className = 'btn btn-xs btn-danger';
b.title = data.message || 'Eroare';
setTimeout(() => {
b.innerHTML = '<i class="bi bi-trash"></i>';
b.className = 'btn btn-xs btn-outline-danger';
b.disabled = false;
b.title = 'Sterge din ROA';
}, 3000);
}
} catch (err) {
b.innerHTML = '<i class="bi bi-exclamation-triangle"></i>';
b.disabled = false;
}
}, {
defaultHtml: '<i class="bi bi-trash"></i>',
loadingText: '',
confirmClass: 'btn-danger',
defaultBtnClass: 'btn-outline-danger'
});
}