feat(flow): map SKU + auto-retry consolidated banner

After saving a SKU mapping, check for SKIPPED orders containing that
SKU and show a floating banner with count + "Importa" button. Batch
retries up to 20 orders and shows result feedback.

Backend:
- get_skipped_orders_with_sku() in sqlite_service.py
- GET /api/orders/by-sku/{sku}/pending endpoint
- POST /api/orders/batch-retry endpoint (max 20, sequential)

Frontend:
- Auto-retry banner after quickMap save with batch import button
- Success/error feedback, auto-dismiss after 15s

Cache-bust: shared.js?v=19

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-27 12:38:10 +00:00
parent 7a789b4fe7
commit b52313faf6
4 changed files with 90 additions and 2 deletions

View File

@@ -344,6 +344,40 @@ async function saveQuickMapping() {
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('quickMapModal')).hide();
if (_qmOnSave) _qmOnSave(sku, mappings);
// Check for SKIPPED orders that can now be imported
try {
const pendingRes = await fetch(`/api/orders/by-sku/${encodeURIComponent(sku)}/pending`);
const pendingData = await pendingRes.json();
if (pendingData.count > 0) {
const banner = document.createElement('div');
banner.className = 'alert alert-info d-flex align-items-center gap-2 mt-2';
banner.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);z-index:1060;min-width:300px;max-width:500px;box-shadow:var(--card-shadow)';
banner.innerHTML = `<i class="bi bi-arrow-clockwise"></i> <span>${pendingData.count} comenzi SKIPPED pot fi importate acum</span> <button class="btn btn-sm btn-primary ms-auto" id="batchRetryBtn">Importa</button> <button class="btn btn-sm btn-outline-secondary" onclick="this.parentElement.remove()">✕</button>`;
document.body.appendChild(banner);
document.getElementById('batchRetryBtn').onclick = async function() {
this.disabled = true;
this.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
try {
const retryRes = await fetch('/api/orders/batch-retry', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({order_numbers: pendingData.order_numbers})
});
const retryData = await retryRes.json();
banner.className = retryData.errors > 0 ? 'alert alert-warning d-flex align-items-center gap-2 mt-2' : 'alert alert-success d-flex align-items-center gap-2 mt-2';
banner.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);z-index:1060;min-width:300px;max-width:500px;box-shadow:var(--card-shadow)';
banner.innerHTML = `<i class="bi bi-check-circle"></i> ${esc(retryData.message)} <button class="btn btn-sm btn-outline-secondary ms-auto" onclick="this.parentElement.remove()">✕</button>`;
setTimeout(() => banner.remove(), 5000);
if (typeof loadDashOrders === 'function') loadDashOrders();
} catch(e) {
banner.innerHTML = `Eroare: ${esc(e.message)} <button class="btn btn-sm btn-outline-secondary ms-auto" onclick="this.parentElement.remove()">✕</button>`;
}
};
setTimeout(() => { if (banner.parentElement) banner.remove(); }, 15000);
}
} catch(e) { /* ignore */ }
} else {
alert('Eroare: ' + (data.error || 'Unknown'));
}