feat(invoice+import): refresh facturi, detalii factura, fix duplicate CODMAT + rollback
- PL/SQL: handle duplicate CODMAT in nom_articole with MAX(id_articol) - import_service: add explicit conn.rollback() on Oracle errors - sync_service: auto-fix stale ERROR orders that exist in Oracle - invoice_service: add data_act (invoice date) from vanzari table - sync router: new POST /api/dashboard/refresh-invoices endpoint - order detail: enrich with invoice data (serie, numar, data factura) - dashboard: refresh invoices button (desktop + mobile icon) - quick map modal: compact single-row layout, pre-populate existing mappings - quick map: link on SKU column instead of CODMAT Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -460,6 +460,29 @@ function renderCodmatCell(item) {
|
||||
).join('');
|
||||
}
|
||||
|
||||
// ── Refresh Invoices ──────────────────────────────
|
||||
|
||||
async function refreshInvoices() {
|
||||
const btn = document.getElementById('btnRefreshInvoices');
|
||||
const btnM = document.getElementById('btnRefreshInvoicesMobile');
|
||||
if (btn) { btn.disabled = true; btn.textContent = '⟳ Se verifica...'; }
|
||||
if (btnM) { btnM.disabled = true; }
|
||||
try {
|
||||
const res = await fetch('/api/dashboard/refresh-invoices', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (data.error) {
|
||||
alert('Eroare: ' + data.error);
|
||||
} else {
|
||||
loadDashOrders();
|
||||
}
|
||||
} catch (err) {
|
||||
alert('Eroare: ' + err.message);
|
||||
} finally {
|
||||
if (btn) { btn.disabled = false; btn.textContent = '↻ Facturi'; }
|
||||
if (btnM) { btnM.disabled = false; }
|
||||
}
|
||||
}
|
||||
|
||||
// ── Order Detail Modal ────────────────────────────
|
||||
|
||||
async function openDashOrderDetail(orderNumber) {
|
||||
@@ -473,6 +496,8 @@ async function openDashOrderDetail(orderNumber) {
|
||||
document.getElementById('detailIdAdresaLivr').textContent = '-';
|
||||
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="6" class="text-center">Se incarca...</td></tr>';
|
||||
document.getElementById('detailError').style.display = 'none';
|
||||
const invInfo = document.getElementById('detailInvoiceInfo');
|
||||
if (invInfo) invInfo.style.display = 'none';
|
||||
const detailItemsTotal = document.getElementById('detailItemsTotal');
|
||||
if (detailItemsTotal) detailItemsTotal.textContent = '-';
|
||||
const detailOrderTotal = document.getElementById('detailOrderTotal');
|
||||
@@ -503,6 +528,19 @@ async function openDashOrderDetail(orderNumber) {
|
||||
document.getElementById('detailIdAdresaFact').textContent = order.id_adresa_facturare || '-';
|
||||
document.getElementById('detailIdAdresaLivr').textContent = order.id_adresa_livrare || '-';
|
||||
|
||||
// Invoice info
|
||||
const invInfo = document.getElementById('detailInvoiceInfo');
|
||||
const inv = order.invoice;
|
||||
if (inv && inv.facturat) {
|
||||
const serie = inv.serie_act || '';
|
||||
const numar = inv.numar_act || '';
|
||||
document.getElementById('detailInvoiceNumber').textContent = serie ? `${serie} ${numar}` : numar;
|
||||
document.getElementById('detailInvoiceDate').textContent = inv.data_act ? fmtDate(inv.data_act) : '-';
|
||||
if (invInfo) invInfo.style.display = '';
|
||||
} else {
|
||||
if (invInfo) invInfo.style.display = 'none';
|
||||
}
|
||||
|
||||
if (order.error_message) {
|
||||
document.getElementById('detailError').textContent = order.error_message;
|
||||
document.getElementById('detailError').style.display = '';
|
||||
@@ -525,18 +563,21 @@ async function openDashOrderDetail(orderNumber) {
|
||||
document.getElementById('detailItemsTotal').textContent = itemsTotal.toFixed(2) + ' lei';
|
||||
document.getElementById('detailOrderTotal').textContent = order.order_total != null ? Number(order.order_total).toFixed(2) + ' lei' : '-';
|
||||
|
||||
// Store items for quick map pre-population
|
||||
window._detailItems = items;
|
||||
|
||||
// Mobile article flat list
|
||||
const mobileContainer = document.getElementById('detailItemsMobile');
|
||||
if (mobileContainer) {
|
||||
mobileContainer.innerHTML = '<div class="detail-item-flat">' + items.map((item, idx) => {
|
||||
const codmatList = item.codmat_details?.length
|
||||
? item.codmat_details.map(d => `<span class="dif-codmat-link" onclick="openQuickMap('${esc(item.sku)}','${esc(item.product_name||'')}','${esc(orderNumber)}')">${esc(d.codmat)}</span>`).join(' ')
|
||||
: `<span class="dif-codmat-link" onclick="openQuickMap('${esc(item.sku)}','${esc(item.product_name||'')}','${esc(orderNumber)}')">${esc(item.codmat || '–')}</span>`;
|
||||
const codmatText = item.codmat_details?.length
|
||||
? item.codmat_details.map(d => `<code>${esc(d.codmat)}</code>`).join(' ')
|
||||
: `<code>${esc(item.codmat || '–')}</code>`;
|
||||
const valoare = (Number(item.price || 0) * Number(item.quantity || 0)).toFixed(2);
|
||||
return `<div class="dif-item">
|
||||
<div class="dif-row">
|
||||
<span class="dif-sku">${esc(item.sku)}</span>
|
||||
${codmatList}
|
||||
<span class="dif-sku dif-codmat-link" onclick="openQuickMap('${esc(item.sku)}','${esc(item.product_name||'')}','${esc(orderNumber)}', ${idx})">${esc(item.sku)}</span>
|
||||
${codmatText}
|
||||
</div>
|
||||
<div class="dif-row">
|
||||
<span class="dif-name">${esc(item.product_name || '–')}</span>
|
||||
@@ -547,13 +588,12 @@ async function openDashOrderDetail(orderNumber) {
|
||||
}).join('') + '</div>';
|
||||
}
|
||||
|
||||
document.getElementById('detailItemsBody').innerHTML = items.map(item => {
|
||||
document.getElementById('detailItemsBody').innerHTML = items.map((item, idx) => {
|
||||
const valoare = (Number(item.price || 0) * Number(item.quantity || 0)).toFixed(2);
|
||||
const codmatCell = `<span class="codmat-link" onclick="openQuickMap('${esc(item.sku)}', '${esc(item.product_name || '')}', '${esc(orderNumber)}')" title="Click pentru mapare">${renderCodmatCell(item)}</span>`;
|
||||
return `<tr>
|
||||
<td><code>${esc(item.sku)}</code></td>
|
||||
<td><code class="codmat-link" onclick="openQuickMap('${esc(item.sku)}', '${esc(item.product_name || '')}', '${esc(orderNumber)}', ${idx})" title="Click pentru mapare">${esc(item.sku)}</code></td>
|
||||
<td>${esc(item.product_name || '-')}</td>
|
||||
<td>${codmatCell}</td>
|
||||
<td>${renderCodmatCell(item)}</td>
|
||||
<td>${item.quantity || 0}</td>
|
||||
<td>${item.price != null ? Number(item.price).toFixed(2) : '-'}</td>
|
||||
<td class="text-end">${valoare}</td>
|
||||
@@ -567,7 +607,7 @@ async function openDashOrderDetail(orderNumber) {
|
||||
|
||||
// ── Quick Map Modal ───────────────────────────────
|
||||
|
||||
function openQuickMap(sku, productName, orderNumber) {
|
||||
function openQuickMap(sku, productName, orderNumber, itemIdx) {
|
||||
currentQmSku = sku;
|
||||
currentQmOrderNumber = orderNumber;
|
||||
document.getElementById('qmSku').textContent = sku;
|
||||
@@ -576,36 +616,41 @@ function openQuickMap(sku, productName, orderNumber) {
|
||||
|
||||
const container = document.getElementById('qmCodmatLines');
|
||||
container.innerHTML = '';
|
||||
addQmCodmatLine();
|
||||
|
||||
// Pre-populate with existing codmat_details if available
|
||||
const item = (window._detailItems || [])[itemIdx];
|
||||
const details = item?.codmat_details;
|
||||
if (details && details.length > 0) {
|
||||
details.forEach(d => {
|
||||
addQmCodmatLine({ codmat: d.codmat, cantitate: d.cantitate_roa, procent: d.procent_pret, denumire: d.denumire });
|
||||
});
|
||||
} else {
|
||||
addQmCodmatLine();
|
||||
}
|
||||
|
||||
new bootstrap.Modal(document.getElementById('quickMapModal')).show();
|
||||
}
|
||||
|
||||
function addQmCodmatLine() {
|
||||
function addQmCodmatLine(prefill) {
|
||||
const container = document.getElementById('qmCodmatLines');
|
||||
const idx = container.children.length;
|
||||
const codmatVal = prefill?.codmat || '';
|
||||
const cantVal = prefill?.cantitate || 1;
|
||||
const pctVal = prefill?.procent || 100;
|
||||
const denumireVal = prefill?.denumire || '';
|
||||
const div = document.createElement('div');
|
||||
div.className = 'border rounded p-2 mb-2 qm-line';
|
||||
div.className = 'qm-line';
|
||||
div.innerHTML = `
|
||||
<div class="mb-2 position-relative">
|
||||
<label class="form-label form-label-sm mb-1">CODMAT (Articol ROA)</label>
|
||||
<input type="text" class="form-control form-control-sm qm-codmat" placeholder="Cauta codmat sau denumire..." autocomplete="off">
|
||||
<div class="autocomplete-dropdown d-none qm-ac-dropdown"></div>
|
||||
<small class="text-muted qm-selected"></small>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-5">
|
||||
<label class="form-label form-label-sm mb-1">Cantitate ROA</label>
|
||||
<input type="number" class="form-control form-control-sm qm-cantitate" value="1" step="0.001" min="0.001">
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<label class="form-label form-label-sm mb-1">Procent Pret (%)</label>
|
||||
<input type="number" class="form-control form-control-sm qm-procent" value="100" step="0.01" min="0" max="100">
|
||||
</div>
|
||||
<div class="col-2 d-flex align-items-end">
|
||||
${idx > 0 ? `<button type="button" class="btn btn-sm btn-outline-danger" onclick="this.closest('.qm-line').remove()"><i class="bi bi-x"></i></button>` : ''}
|
||||
</div>
|
||||
<div class="qm-row">
|
||||
<div class="qm-codmat-wrap position-relative">
|
||||
<input type="text" class="form-control form-control-sm qm-codmat" placeholder="CODMAT..." autocomplete="off" value="${escHtml(codmatVal)}">
|
||||
<div class="autocomplete-dropdown d-none qm-ac-dropdown"></div>
|
||||
</div>
|
||||
<input type="number" class="form-control form-control-sm qm-cantitate" value="${cantVal}" step="0.001" min="0.001" title="Cantitate ROA" style="width:70px">
|
||||
<input type="number" class="form-control form-control-sm qm-procent" value="${pctVal}" step="0.01" min="0" max="100" title="Procent %" style="width:70px">
|
||||
${idx > 0 ? `<button type="button" class="btn btn-sm btn-outline-danger qm-rm-btn" onclick="this.closest('.qm-line').remove()"><i class="bi bi-x"></i></button>` : '<span style="width:30px"></span>'}
|
||||
</div>
|
||||
<div class="qm-selected text-muted" style="font-size:0.75rem;padding-left:2px">${escHtml(denumireVal)}</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user