refactor(modal): extract order detail to shared.js
Move duplicated order detail modal logic from dashboard.js and logs.js into a shared renderOrderDetailModal() function in shared.js. Move modal HTML from dashboard.html and logs.html into base.html. Shared functions: renderCodmatCell, orderStatusBadge, fmtCost, fmtNum, computeDiscountSplit, renderReceipt. Both pages now call the shared modal with page-specific quick map callbacks. Net -152 lines. Logs page gains invoice info, TVA column, and receipt footer that were previously dashboard-only. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -432,14 +432,6 @@ function escHtml(s) {
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
// Alias kept for backward compat with inline handlers in modal
|
||||
function esc(s) { return escHtml(s); }
|
||||
|
||||
function fmtCost(v) {
|
||||
return v > 0 ? Number(v).toFixed(2) : '–';
|
||||
}
|
||||
|
||||
|
||||
function statusLabelText(status) {
|
||||
switch ((status || '').toUpperCase()) {
|
||||
case 'IMPORTED': return 'Importat';
|
||||
@@ -450,40 +442,12 @@ function statusLabelText(status) {
|
||||
}
|
||||
}
|
||||
|
||||
function orderStatusBadge(status) {
|
||||
switch ((status || '').toUpperCase()) {
|
||||
case 'IMPORTED': return '<span class="badge bg-success">Importat</span>';
|
||||
case 'ALREADY_IMPORTED': return '<span class="badge bg-info">Deja importat</span>';
|
||||
case 'SKIPPED': return '<span class="badge bg-warning">Omis</span>';
|
||||
case 'ERROR': return '<span class="badge bg-danger">Eroare</span>';
|
||||
case 'CANCELLED': return '<span class="badge bg-secondary">Anulat</span>';
|
||||
case 'DELETED_IN_ROA': return '<span class="badge bg-dark">Sters din ROA</span>';
|
||||
default: return `<span class="badge bg-secondary">${esc(status)}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
function invoiceDot(order) {
|
||||
if (order.status !== 'IMPORTED' && order.status !== 'ALREADY_IMPORTED') return '–';
|
||||
if (order.invoice && order.invoice.facturat) return '<span class="dot dot-green" title="Facturat"></span>';
|
||||
return '<span class="dot dot-red" title="Nefacturat"></span>';
|
||||
}
|
||||
|
||||
function renderCodmatCell(item) {
|
||||
if (!item.codmat_details || item.codmat_details.length === 0) {
|
||||
return `<code>${esc(item.codmat || '-')}</code>`;
|
||||
}
|
||||
if (item.codmat_details.length === 1) {
|
||||
const d = item.codmat_details[0];
|
||||
if (d.direct) {
|
||||
return `<code>${esc(d.codmat)}</code> <span class="badge bg-secondary" style="font-size:0.6rem;vertical-align:middle">direct</span>`;
|
||||
}
|
||||
return `<code>${esc(d.codmat)}</code>`;
|
||||
}
|
||||
return item.codmat_details.map(d =>
|
||||
`<div class="small"><code>${esc(d.codmat)}</code> <span class="text-muted">\xd7${d.cantitate_roa}</span></div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
// ── Refresh Invoices ──────────────────────────────
|
||||
|
||||
async function refreshInvoices() {
|
||||
@@ -509,262 +473,12 @@ async function refreshInvoices() {
|
||||
|
||||
// ── Order Detail Modal ────────────────────────────
|
||||
|
||||
async function openDashOrderDetail(orderNumber) {
|
||||
document.getElementById('detailOrderNumber').textContent = '#' + orderNumber;
|
||||
document.getElementById('detailCustomer').textContent = '...';
|
||||
document.getElementById('detailDate').textContent = '';
|
||||
document.getElementById('detailStatus').innerHTML = '';
|
||||
document.getElementById('detailIdComanda').textContent = '-';
|
||||
document.getElementById('detailIdPartener').textContent = '-';
|
||||
document.getElementById('detailIdAdresaFact').textContent = '-';
|
||||
document.getElementById('detailIdAdresaLivr').textContent = '-';
|
||||
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="7" class="text-center">Se incarca...</td></tr>';
|
||||
document.getElementById('detailError').style.display = 'none';
|
||||
document.getElementById('detailReceipt').innerHTML = '';
|
||||
document.getElementById('detailReceiptMobile').innerHTML = '';
|
||||
const invInfo = document.getElementById('detailInvoiceInfo');
|
||||
if (invInfo) invInfo.style.display = 'none';
|
||||
const mobileContainer = document.getElementById('detailItemsMobile');
|
||||
if (mobileContainer) mobileContainer.innerHTML = '';
|
||||
|
||||
const modalEl = document.getElementById('orderDetailModal');
|
||||
const existing = bootstrap.Modal.getInstance(modalEl);
|
||||
if (existing) { existing.show(); } else { new bootstrap.Modal(modalEl).show(); }
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/sync/order/${encodeURIComponent(orderNumber)}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
document.getElementById('detailError').textContent = data.error;
|
||||
document.getElementById('detailError').style.display = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const order = data.order || {};
|
||||
document.getElementById('detailCustomer').textContent = order.customer_name || '-';
|
||||
document.getElementById('detailDate').textContent = fmtDate(order.order_date);
|
||||
document.getElementById('detailStatus').innerHTML = orderStatusBadge(order.status);
|
||||
document.getElementById('detailIdComanda').textContent = order.id_comanda || '-';
|
||||
document.getElementById('detailIdPartener').textContent = order.id_partener || '-';
|
||||
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 = '';
|
||||
}
|
||||
|
||||
const items = data.items || [];
|
||||
if (items.length === 0) {
|
||||
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="7" class="text-center text-muted">Niciun articol</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Store items for quick map pre-population
|
||||
window._detailItems = items;
|
||||
|
||||
// Mobile article flat list
|
||||
const mobileContainer = document.getElementById('detailItemsMobile');
|
||||
if (mobileContainer) {
|
||||
let mobileHtml = items.map((item, idx) => {
|
||||
const codmatText = item.codmat_details?.length
|
||||
? item.codmat_details.map(d => `<code>${esc(d.codmat)}</code>${d.direct ? ' <span class="badge bg-secondary" style="font-size:0.55rem">direct</span>' : ''}`).join(' ')
|
||||
: `<code>${esc(item.codmat || '–')}</code>`;
|
||||
const valoare = (Number(item.price || 0) * Number(item.quantity || 0));
|
||||
return `<div class="dif-item">
|
||||
<div class="dif-row">
|
||||
<span class="dif-sku dif-codmat-link" onclick="openDashQuickMap('${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>
|
||||
<span class="dif-qty">x${item.quantity || 0}</span>
|
||||
<span class="dif-val">${fmtNum(valoare)} lei</span>
|
||||
<span class="dif-vat text-muted" style="font-size:0.75rem">TVA ${item.vat != null ? Number(item.vat) : '?'}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// Transport row (mobile)
|
||||
if (order.delivery_cost > 0) {
|
||||
const tVat = order.transport_vat || '21';
|
||||
mobileHtml += `<div class="dif-item" style="opacity:0.7">
|
||||
<div class="dif-row">
|
||||
<span class="dif-name text-muted">Transport</span>
|
||||
<span class="dif-qty">x1</span>
|
||||
<span class="dif-val">${fmtNum(order.delivery_cost)} lei</span>
|
||||
<span class="dif-vat text-muted" style="font-size:0.75rem">TVA ${tVat}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Discount rows (mobile)
|
||||
if (order.discount_total > 0) {
|
||||
const discSplit = computeDiscountSplit(items, order);
|
||||
if (discSplit) {
|
||||
Object.entries(discSplit)
|
||||
.sort(([a], [b]) => Number(a) - Number(b))
|
||||
.forEach(([rate, amt]) => {
|
||||
if (amt > 0) mobileHtml += `<div class="dif-item" style="opacity:0.7">
|
||||
<div class="dif-row">
|
||||
<span class="dif-name text-muted">Discount</span>
|
||||
<span class="dif-qty">x\u20131</span>
|
||||
<span class="dif-val">${fmtNum(amt)} lei</span>
|
||||
<span class="dif-vat text-muted" style="font-size:0.75rem">TVA ${Number(rate)}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
} else {
|
||||
mobileHtml += `<div class="dif-item" style="opacity:0.7">
|
||||
<div class="dif-row">
|
||||
<span class="dif-name text-muted">Discount</span>
|
||||
<span class="dif-qty">x\u20131</span>
|
||||
<span class="dif-val">${fmtNum(order.discount_total)} lei</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
mobileContainer.innerHTML = '<div class="detail-item-flat">' + mobileHtml + '</div>';
|
||||
}
|
||||
|
||||
let tableHtml = items.map((item, idx) => {
|
||||
const valoare = Number(item.price || 0) * Number(item.quantity || 0);
|
||||
return `<tr>
|
||||
<td><code class="codmat-link" onclick="openDashQuickMap('${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>${renderCodmatCell(item)}</td>
|
||||
<td class="text-end">${item.quantity || 0}</td>
|
||||
<td class="text-end">${item.price != null ? fmtNum(item.price) : '-'}</td>
|
||||
<td class="text-end">${item.vat != null ? Number(item.vat) : '-'}</td>
|
||||
<td class="text-end">${fmtNum(valoare)}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
// Transport row
|
||||
if (order.delivery_cost > 0) {
|
||||
const tVat = order.transport_vat || '21';
|
||||
const tCodmat = order.transport_codmat || '';
|
||||
tableHtml += `<tr class="table-light">
|
||||
<td></td><td class="text-muted">Transport</td>
|
||||
<td>${tCodmat ? '<code>' + esc(tCodmat) + '</code>' : ''}</td>
|
||||
<td class="text-end">1</td><td class="text-end">${fmtNum(order.delivery_cost)}</td>
|
||||
<td class="text-end">${tVat}</td><td class="text-end">${fmtNum(order.delivery_cost)}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
// Discount rows (split by VAT rate)
|
||||
if (order.discount_total > 0) {
|
||||
const dCodmat = order.discount_codmat || '';
|
||||
const discSplit = computeDiscountSplit(items, order);
|
||||
if (discSplit) {
|
||||
Object.entries(discSplit)
|
||||
.sort(([a], [b]) => Number(a) - Number(b))
|
||||
.forEach(([rate, amt]) => {
|
||||
if (amt > 0) tableHtml += `<tr class="table-light">
|
||||
<td></td><td class="text-muted">Discount</td>
|
||||
<td>${dCodmat ? '<code>' + esc(dCodmat) + '</code>' : ''}</td>
|
||||
<td class="text-end">\u20131</td><td class="text-end">${fmtNum(amt)}</td>
|
||||
<td class="text-end">${Number(rate)}</td><td class="text-end">\u2013${fmtNum(amt)}</td>
|
||||
</tr>`;
|
||||
});
|
||||
} else {
|
||||
tableHtml += `<tr class="table-light">
|
||||
<td></td><td class="text-muted">Discount</td>
|
||||
<td>${dCodmat ? '<code>' + esc(dCodmat) + '</code>' : ''}</td>
|
||||
<td class="text-end">\u20131</td><td class="text-end">${fmtNum(order.discount_total)}</td>
|
||||
<td class="text-end">-</td><td class="text-end">\u2013${fmtNum(order.discount_total)}</td>
|
||||
</tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('detailItemsBody').innerHTML = tableHtml;
|
||||
|
||||
// Receipt footer (just total)
|
||||
renderReceipt(items, order);
|
||||
} catch (err) {
|
||||
document.getElementById('detailError').textContent = err.message;
|
||||
document.getElementById('detailError').style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function fmtNum(v) {
|
||||
return Number(v).toLocaleString('ro-RO', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function computeDiscountSplit(items, order) {
|
||||
if (order.discount_split && typeof order.discount_split === 'object')
|
||||
return order.discount_split;
|
||||
|
||||
// Compute proportionally from items by VAT rate
|
||||
const byRate = {};
|
||||
items.forEach(item => {
|
||||
const rate = item.vat != null ? Number(item.vat) : null;
|
||||
if (rate === null) return;
|
||||
if (!byRate[rate]) byRate[rate] = 0;
|
||||
byRate[rate] += Number(item.price || 0) * Number(item.quantity || 0);
|
||||
function openDashOrderDetail(orderNumber) {
|
||||
_sharedModalQuickMapFn = openDashQuickMap;
|
||||
renderOrderDetailModal(orderNumber, {
|
||||
onQuickMap: openDashQuickMap,
|
||||
onAfterRender: function() { /* nothing extra needed */ }
|
||||
});
|
||||
const rates = Object.keys(byRate).sort((a, b) => Number(a) - Number(b));
|
||||
if (rates.length === 0) return null;
|
||||
|
||||
const grandTotal = rates.reduce((s, r) => s + byRate[r], 0);
|
||||
if (grandTotal <= 0) return null;
|
||||
|
||||
const split = {};
|
||||
let remaining = order.discount_total;
|
||||
rates.forEach((rate, i) => {
|
||||
if (i === rates.length - 1) {
|
||||
split[rate] = Math.round(remaining * 100) / 100;
|
||||
} else {
|
||||
const amt = Math.round(order.discount_total * byRate[rate] / grandTotal * 100) / 100;
|
||||
split[rate] = amt;
|
||||
remaining -= amt;
|
||||
}
|
||||
});
|
||||
return split;
|
||||
}
|
||||
|
||||
function renderReceipt(items, order) {
|
||||
const desktop = document.getElementById('detailReceipt');
|
||||
const mobile = document.getElementById('detailReceiptMobile');
|
||||
if (!items.length) {
|
||||
desktop.innerHTML = '';
|
||||
mobile.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const articole = items.reduce((s, i) => s + Number(i.price || 0) * Number(i.quantity || 0), 0);
|
||||
const discount = Number(order.discount_total || 0);
|
||||
const transport = Number(order.delivery_cost || 0);
|
||||
const total = order.order_total != null ? fmtNum(order.order_total) : '-';
|
||||
|
||||
// Desktop: full labels
|
||||
let dHtml = `<span class="text-muted">Articole: <strong class="text-body">${fmtNum(articole)}</strong></span>`;
|
||||
if (discount > 0) dHtml += `<span class="text-muted">Discount: <strong class="text-danger">\u2013${fmtNum(discount)}</strong></span>`;
|
||||
if (transport > 0) dHtml += `<span class="text-muted">Transport: <strong class="text-body">${fmtNum(transport)}</strong></span>`;
|
||||
dHtml += `<span>Total: <strong>${total} lei</strong></span>`;
|
||||
desktop.innerHTML = dHtml;
|
||||
|
||||
// Mobile: shorter labels
|
||||
let mHtml = `<span class="text-muted">Art: <strong class="text-body">${fmtNum(articole)}</strong></span>`;
|
||||
if (discount > 0) mHtml += `<span class="text-muted">Disc: <strong class="text-danger">\u2013${fmtNum(discount)}</strong></span>`;
|
||||
if (transport > 0) mHtml += `<span class="text-muted">Transp: <strong class="text-body">${fmtNum(transport)}</strong></span>`;
|
||||
mHtml += `<span>Total: <strong>${total} lei</strong></span>`;
|
||||
mobile.innerHTML = mHtml;
|
||||
}
|
||||
|
||||
// ── Quick Map Modal (uses shared openQuickMap) ───
|
||||
|
||||
@@ -8,10 +8,6 @@ let ordersPage = 1;
|
||||
let ordersSortColumn = 'order_date';
|
||||
let ordersSortDirection = 'desc';
|
||||
|
||||
function fmtCost(v) {
|
||||
return v > 0 ? Number(v).toFixed(2) : '–';
|
||||
}
|
||||
|
||||
function fmtDuration(startedAt, finishedAt) {
|
||||
if (!startedAt || !finishedAt) return '-';
|
||||
const diffMs = new Date(finishedAt) - new Date(startedAt);
|
||||
@@ -30,17 +26,6 @@ function runStatusBadge(status) {
|
||||
}
|
||||
}
|
||||
|
||||
function orderStatusBadge(status) {
|
||||
switch ((status || '').toUpperCase()) {
|
||||
case 'IMPORTED': return '<span class="badge bg-success">Importat</span>';
|
||||
case 'ALREADY_IMPORTED': return '<span class="badge bg-info">Deja importat</span>';
|
||||
case 'SKIPPED': return '<span class="badge bg-warning">Omis</span>';
|
||||
case 'ERROR': return '<span class="badge bg-danger">Eroare</span>';
|
||||
case 'DELETED_IN_ROA': return '<span class="badge bg-dark">Sters din ROA</span>';
|
||||
default: return `<span class="badge bg-secondary">${esc(status)}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
function logStatusText(status) {
|
||||
switch ((status || '').toUpperCase()) {
|
||||
case 'IMPORTED': return 'Importat';
|
||||
@@ -296,125 +281,17 @@ async function fetchTextLog(runId) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Multi-CODMAT helper (D1) ─────────────────────
|
||||
|
||||
function renderCodmatCell(item) {
|
||||
if (!item.codmat_details || item.codmat_details.length === 0) {
|
||||
return `<code>${esc(item.codmat || '-')}</code>`;
|
||||
}
|
||||
if (item.codmat_details.length === 1) {
|
||||
const d = item.codmat_details[0];
|
||||
return `<code>${esc(d.codmat)}</code>`;
|
||||
}
|
||||
// Multi-CODMAT: compact list
|
||||
return item.codmat_details.map(d =>
|
||||
`<div class="small"><code>${esc(d.codmat)}</code> <span class="text-muted">\xd7${d.cantitate_roa}</span></div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
// ── Order Detail Modal (R9) ─────────────────────
|
||||
|
||||
async function openOrderDetail(orderNumber) {
|
||||
document.getElementById('detailOrderNumber').textContent = '#' + orderNumber;
|
||||
document.getElementById('detailCustomer').textContent = '...';
|
||||
document.getElementById('detailDate').textContent = '';
|
||||
document.getElementById('detailStatus').innerHTML = '';
|
||||
document.getElementById('detailIdComanda').textContent = '-';
|
||||
document.getElementById('detailIdPartener').textContent = '-';
|
||||
document.getElementById('detailIdAdresaFact').textContent = '-';
|
||||
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 detailItemsTotal = document.getElementById('detailItemsTotal');
|
||||
if (detailItemsTotal) detailItemsTotal.textContent = '-';
|
||||
const detailOrderTotal = document.getElementById('detailOrderTotal');
|
||||
if (detailOrderTotal) detailOrderTotal.textContent = '-';
|
||||
const mobileContainer = document.getElementById('detailItemsMobile');
|
||||
if (mobileContainer) mobileContainer.innerHTML = '';
|
||||
|
||||
const modalEl = document.getElementById('orderDetailModal');
|
||||
const existing = bootstrap.Modal.getInstance(modalEl);
|
||||
if (existing) { existing.show(); } else { new bootstrap.Modal(modalEl).show(); }
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/sync/order/${encodeURIComponent(orderNumber)}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
document.getElementById('detailError').textContent = data.error;
|
||||
document.getElementById('detailError').style.display = '';
|
||||
return;
|
||||
function openOrderDetail(orderNumber) {
|
||||
_sharedModalQuickMapFn = function(sku, productName, orderNum, itemIdx) {
|
||||
openLogsQuickMap(sku, productName, orderNum);
|
||||
};
|
||||
renderOrderDetailModal(orderNumber, {
|
||||
onQuickMap: function(sku, productName, orderNum, itemIdx) {
|
||||
openLogsQuickMap(sku, productName, orderNum);
|
||||
}
|
||||
|
||||
const order = data.order || {};
|
||||
document.getElementById('detailCustomer').textContent = order.customer_name || '-';
|
||||
document.getElementById('detailDate').textContent = fmtDate(order.order_date);
|
||||
document.getElementById('detailStatus').innerHTML = orderStatusBadge(order.status);
|
||||
document.getElementById('detailIdComanda').textContent = order.id_comanda || '-';
|
||||
document.getElementById('detailIdPartener').textContent = order.id_partener || '-';
|
||||
document.getElementById('detailIdAdresaFact').textContent = order.id_adresa_facturare || '-';
|
||||
document.getElementById('detailIdAdresaLivr').textContent = order.id_adresa_livrare || '-';
|
||||
|
||||
if (order.error_message) {
|
||||
document.getElementById('detailError').textContent = order.error_message;
|
||||
document.getElementById('detailError').style.display = '';
|
||||
}
|
||||
|
||||
const dlvEl = document.getElementById('detailDeliveryCost');
|
||||
if (dlvEl) dlvEl.textContent = order.delivery_cost > 0 ? Number(order.delivery_cost).toFixed(2) + ' lei' : '–';
|
||||
|
||||
const dscEl = document.getElementById('detailDiscount');
|
||||
if (dscEl) dscEl.textContent = order.discount_total > 0 ? '–' + Number(order.discount_total).toFixed(2) + ' lei' : '–';
|
||||
|
||||
const items = data.items || [];
|
||||
if (items.length === 0) {
|
||||
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="6" class="text-center text-muted">Niciun articol</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Update totals row
|
||||
const itemsTotal = items.reduce((sum, item) => sum + (Number(item.price || 0) * Number(item.quantity || 0)), 0);
|
||||
document.getElementById('detailItemsTotal').textContent = itemsTotal.toFixed(2) + ' lei';
|
||||
document.getElementById('detailOrderTotal').textContent = order.order_total != null ? Number(order.order_total).toFixed(2) + ' lei' : '-';
|
||||
|
||||
// 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="openLogsQuickMap('${esc(item.sku)}','${esc(item.product_name||'')}','${esc(orderNumber)}')">${esc(d.codmat)}</span>`).join(' ')
|
||||
: `<span class="dif-codmat-link" onclick="openLogsQuickMap('${esc(item.sku)}','${esc(item.product_name||'')}','${esc(orderNumber)}')">${esc(item.codmat || '–')}</span>`;
|
||||
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}
|
||||
</div>
|
||||
<div class="dif-row">
|
||||
<span class="dif-name">${esc(item.product_name || '–')}</span>
|
||||
<span class="dif-qty">x${item.quantity || 0}</span>
|
||||
<span class="dif-val">${valoare} lei</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('') + '</div>';
|
||||
}
|
||||
|
||||
document.getElementById('detailItemsBody').innerHTML = items.map(item => {
|
||||
const valoare = (Number(item.price || 0) * Number(item.quantity || 0)).toFixed(2);
|
||||
const codmatCell = `<span class="codmat-link" onclick="openLogsQuickMap('${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>${esc(item.product_name || '-')}</td>
|
||||
<td>${codmatCell}</td>
|
||||
<td>${item.quantity || 0}</td>
|
||||
<td>${item.price != null ? Number(item.price).toFixed(2) : '-'}</td>
|
||||
<td class="text-end">${valoare}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
} catch (err) {
|
||||
document.getElementById('detailError').textContent = err.message;
|
||||
document.getElementById('detailError').style.display = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Quick Map Modal (uses shared openQuickMap) ───
|
||||
|
||||
@@ -352,6 +352,319 @@ async function saveQuickMapping() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Shared helpers (moved from dashboard.js/logs.js) ─
|
||||
|
||||
function fmtCost(v) {
|
||||
return v > 0 ? Number(v).toFixed(2) : '–';
|
||||
}
|
||||
|
||||
function fmtNum(v) {
|
||||
return Number(v).toLocaleString('ro-RO', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
}
|
||||
|
||||
function orderStatusBadge(status) {
|
||||
switch ((status || '').toUpperCase()) {
|
||||
case 'IMPORTED': return '<span class="badge bg-success">Importat</span>';
|
||||
case 'ALREADY_IMPORTED': return '<span class="badge bg-info">Deja importat</span>';
|
||||
case 'SKIPPED': return '<span class="badge bg-warning">Omis</span>';
|
||||
case 'ERROR': return '<span class="badge bg-danger">Eroare</span>';
|
||||
case 'CANCELLED': return '<span class="badge bg-secondary">Anulat</span>';
|
||||
case 'DELETED_IN_ROA': return '<span class="badge bg-dark">Sters din ROA</span>';
|
||||
default: return `<span class="badge bg-secondary">${esc(status)}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderCodmatCell(item) {
|
||||
if (!item.codmat_details || item.codmat_details.length === 0) {
|
||||
return `<code>${esc(item.codmat || '-')}</code>`;
|
||||
}
|
||||
if (item.codmat_details.length === 1) {
|
||||
const d = item.codmat_details[0];
|
||||
if (d.direct) {
|
||||
return `<code>${esc(d.codmat)}</code> <span class="badge bg-secondary" style="font-size:0.6rem;vertical-align:middle">direct</span>`;
|
||||
}
|
||||
return `<code>${esc(d.codmat)}</code>`;
|
||||
}
|
||||
return item.codmat_details.map(d =>
|
||||
`<div class="small"><code>${esc(d.codmat)}</code> <span class="text-muted">\xd7${d.cantitate_roa}</span></div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
function computeDiscountSplit(items, order) {
|
||||
if (order.discount_split && typeof order.discount_split === 'object')
|
||||
return order.discount_split;
|
||||
|
||||
const byRate = {};
|
||||
items.forEach(item => {
|
||||
const rate = item.vat != null ? Number(item.vat) : null;
|
||||
if (rate === null) return;
|
||||
if (!byRate[rate]) byRate[rate] = 0;
|
||||
byRate[rate] += Number(item.price || 0) * Number(item.quantity || 0);
|
||||
});
|
||||
const rates = Object.keys(byRate).sort((a, b) => Number(a) - Number(b));
|
||||
if (rates.length === 0) return null;
|
||||
|
||||
const grandTotal = rates.reduce((s, r) => s + byRate[r], 0);
|
||||
if (grandTotal <= 0) return null;
|
||||
|
||||
const split = {};
|
||||
let remaining = order.discount_total;
|
||||
rates.forEach((rate, i) => {
|
||||
if (i === rates.length - 1) {
|
||||
split[rate] = Math.round(remaining * 100) / 100;
|
||||
} else {
|
||||
const amt = Math.round(order.discount_total * byRate[rate] / grandTotal * 100) / 100;
|
||||
split[rate] = amt;
|
||||
remaining -= amt;
|
||||
}
|
||||
});
|
||||
return split;
|
||||
}
|
||||
|
||||
function _renderReceipt(items, order) {
|
||||
const desktop = document.getElementById('detailReceipt');
|
||||
const mobile = document.getElementById('detailReceiptMobile');
|
||||
if (!desktop && !mobile) return;
|
||||
if (!items.length) {
|
||||
if (desktop) desktop.innerHTML = '';
|
||||
if (mobile) mobile.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const articole = items.reduce((s, i) => s + Number(i.price || 0) * Number(i.quantity || 0), 0);
|
||||
const discount = Number(order.discount_total || 0);
|
||||
const transport = Number(order.delivery_cost || 0);
|
||||
const total = order.order_total != null ? fmtNum(order.order_total) : '-';
|
||||
|
||||
let dHtml = `<span class="text-muted">Articole: <strong class="text-body">${fmtNum(articole)}</strong></span>`;
|
||||
if (discount > 0) dHtml += `<span class="text-muted">Discount: <strong class="text-danger">\u2013${fmtNum(discount)}</strong></span>`;
|
||||
if (transport > 0) dHtml += `<span class="text-muted">Transport: <strong class="text-body">${fmtNum(transport)}</strong></span>`;
|
||||
dHtml += `<span>Total: <strong>${total} lei</strong></span>`;
|
||||
if (desktop) desktop.innerHTML = dHtml;
|
||||
|
||||
let mHtml = `<span class="text-muted">Art: <strong class="text-body">${fmtNum(articole)}</strong></span>`;
|
||||
if (discount > 0) mHtml += `<span class="text-muted">Disc: <strong class="text-danger">\u2013${fmtNum(discount)}</strong></span>`;
|
||||
if (transport > 0) mHtml += `<span class="text-muted">Transp: <strong class="text-body">${fmtNum(transport)}</strong></span>`;
|
||||
mHtml += `<span>Total: <strong>${total} lei</strong></span>`;
|
||||
if (mobile) mobile.innerHTML = mHtml;
|
||||
}
|
||||
|
||||
// ── Order Detail Modal (shared) ──────────────────
|
||||
/**
|
||||
* Render and show the order detail modal.
|
||||
* @param {string} orderNumber
|
||||
* @param {object} opts
|
||||
* @param {function} opts.onQuickMap - (sku, productName, orderNumber, itemIdx) => void
|
||||
* @param {function} [opts.onAfterRender] - (order, items) => void
|
||||
*/
|
||||
async function renderOrderDetailModal(orderNumber, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
// Reset modal state
|
||||
document.getElementById('detailOrderNumber').textContent = '#' + orderNumber;
|
||||
document.getElementById('detailCustomer').textContent = '...';
|
||||
document.getElementById('detailDate').textContent = '';
|
||||
document.getElementById('detailStatus').innerHTML = '';
|
||||
document.getElementById('detailIdComanda').textContent = '-';
|
||||
document.getElementById('detailIdPartener').textContent = '-';
|
||||
document.getElementById('detailIdAdresaFact').textContent = '-';
|
||||
document.getElementById('detailIdAdresaLivr').textContent = '-';
|
||||
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="7" class="text-center">Se incarca...</td></tr>';
|
||||
document.getElementById('detailError').style.display = 'none';
|
||||
const receiptEl = document.getElementById('detailReceipt');
|
||||
if (receiptEl) receiptEl.innerHTML = '';
|
||||
const receiptMEl = document.getElementById('detailReceiptMobile');
|
||||
if (receiptMEl) receiptMEl.innerHTML = '';
|
||||
const invInfo = document.getElementById('detailInvoiceInfo');
|
||||
if (invInfo) invInfo.style.display = 'none';
|
||||
const mobileContainer = document.getElementById('detailItemsMobile');
|
||||
if (mobileContainer) mobileContainer.innerHTML = '';
|
||||
|
||||
const modalEl = document.getElementById('orderDetailModal');
|
||||
const existing = bootstrap.Modal.getInstance(modalEl);
|
||||
if (existing) { existing.show(); } else { new bootstrap.Modal(modalEl).show(); }
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/sync/order/${encodeURIComponent(orderNumber)}`);
|
||||
const data = await res.json();
|
||||
|
||||
if (data.error) {
|
||||
document.getElementById('detailError').textContent = data.error;
|
||||
document.getElementById('detailError').style.display = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const order = data.order || {};
|
||||
document.getElementById('detailCustomer').textContent = order.customer_name || '-';
|
||||
document.getElementById('detailDate').textContent = fmtDate(order.order_date);
|
||||
document.getElementById('detailStatus').innerHTML = orderStatusBadge(order.status);
|
||||
document.getElementById('detailIdComanda').textContent = order.id_comanda || '-';
|
||||
document.getElementById('detailIdPartener').textContent = order.id_partener || '-';
|
||||
document.getElementById('detailIdAdresaFact').textContent = order.id_adresa_facturare || '-';
|
||||
document.getElementById('detailIdAdresaLivr').textContent = order.id_adresa_livrare || '-';
|
||||
|
||||
// Invoice info
|
||||
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 = '';
|
||||
}
|
||||
|
||||
if (order.error_message) {
|
||||
document.getElementById('detailError').textContent = order.error_message;
|
||||
document.getElementById('detailError').style.display = '';
|
||||
}
|
||||
|
||||
const items = data.items || [];
|
||||
if (items.length === 0) {
|
||||
document.getElementById('detailItemsBody').innerHTML = '<tr><td colspan="7" class="text-center text-muted">Niciun articol</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Store items for quick map pre-population
|
||||
window._detailItems = items;
|
||||
|
||||
const qmFn = opts.onQuickMap ? opts.onQuickMap.name || '_sharedQuickMap' : null;
|
||||
|
||||
// Mobile article flat list
|
||||
if (mobileContainer) {
|
||||
let mobileHtml = items.map((item, idx) => {
|
||||
const codmatText = item.codmat_details?.length
|
||||
? item.codmat_details.map(d => `<code>${esc(d.codmat)}</code>${d.direct ? ' <span class="badge bg-secondary" style="font-size:0.55rem">direct</span>' : ''}`).join(' ')
|
||||
: `<code>${esc(item.codmat || '–')}</code>`;
|
||||
const valoare = (Number(item.price || 0) * Number(item.quantity || 0));
|
||||
const clickAttr = opts.onQuickMap ? `onclick="_sharedModalQuickMap('${esc(item.sku)}','${esc(item.product_name||'')}','${esc(orderNumber)}',${idx})"` : '';
|
||||
return `<div class="dif-item">
|
||||
<div class="dif-row">
|
||||
<span class="dif-sku${opts.onQuickMap ? ' dif-codmat-link' : ''}" ${clickAttr}>${esc(item.sku)}</span>
|
||||
${codmatText}
|
||||
</div>
|
||||
<div class="dif-row">
|
||||
<span class="dif-name">${esc(item.product_name || '–')}</span>
|
||||
<span class="dif-qty">x${item.quantity || 0}</span>
|
||||
<span class="dif-val">${fmtNum(valoare)} lei</span>
|
||||
<span class="dif-vat text-muted" style="font-size:0.75rem">TVA ${item.vat != null ? Number(item.vat) : '?'}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// Transport row (mobile)
|
||||
if (order.delivery_cost > 0) {
|
||||
const tVat = order.transport_vat || '21';
|
||||
mobileHtml += `<div class="dif-item" style="opacity:0.7">
|
||||
<div class="dif-row">
|
||||
<span class="dif-name text-muted">Transport</span>
|
||||
<span class="dif-qty">x1</span>
|
||||
<span class="dif-val">${fmtNum(order.delivery_cost)} lei</span>
|
||||
<span class="dif-vat text-muted" style="font-size:0.75rem">TVA ${tVat}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Discount rows (mobile)
|
||||
if (order.discount_total > 0) {
|
||||
const discSplit = computeDiscountSplit(items, order);
|
||||
if (discSplit) {
|
||||
Object.entries(discSplit)
|
||||
.sort(([a], [b]) => Number(a) - Number(b))
|
||||
.forEach(([rate, amt]) => {
|
||||
if (amt > 0) mobileHtml += `<div class="dif-item" style="opacity:0.7">
|
||||
<div class="dif-row">
|
||||
<span class="dif-name text-muted">Discount</span>
|
||||
<span class="dif-qty">x\u20131</span>
|
||||
<span class="dif-val">${fmtNum(amt)} lei</span>
|
||||
<span class="dif-vat text-muted" style="font-size:0.75rem">TVA ${Number(rate)}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
} else {
|
||||
mobileHtml += `<div class="dif-item" style="opacity:0.7">
|
||||
<div class="dif-row">
|
||||
<span class="dif-name text-muted">Discount</span>
|
||||
<span class="dif-qty">x\u20131</span>
|
||||
<span class="dif-val">${fmtNum(order.discount_total)} lei</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
mobileContainer.innerHTML = '<div class="detail-item-flat">' + mobileHtml + '</div>';
|
||||
}
|
||||
|
||||
// Desktop items table
|
||||
const clickAttrFn = (item, idx) => opts.onQuickMap
|
||||
? `onclick="_sharedModalQuickMap('${esc(item.sku)}', '${esc(item.product_name || '')}', '${esc(orderNumber)}', ${idx})" title="Click pentru mapare"`
|
||||
: '';
|
||||
|
||||
let tableHtml = items.map((item, idx) => {
|
||||
const valoare = Number(item.price || 0) * Number(item.quantity || 0);
|
||||
return `<tr>
|
||||
<td><code class="${opts.onQuickMap ? 'codmat-link' : ''}" ${clickAttrFn(item, idx)}>${esc(item.sku)}</code></td>
|
||||
<td>${esc(item.product_name || '-')}</td>
|
||||
<td>${renderCodmatCell(item)}</td>
|
||||
<td class="text-end">${item.quantity || 0}</td>
|
||||
<td class="text-end">${item.price != null ? fmtNum(item.price) : '-'}</td>
|
||||
<td class="text-end">${item.vat != null ? Number(item.vat) : '-'}</td>
|
||||
<td class="text-end">${fmtNum(valoare)}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
// Transport row
|
||||
if (order.delivery_cost > 0) {
|
||||
const tVat = order.transport_vat || '21';
|
||||
const tCodmat = order.transport_codmat || '';
|
||||
tableHtml += `<tr class="table-light">
|
||||
<td></td><td class="text-muted">Transport</td>
|
||||
<td>${tCodmat ? '<code>' + esc(tCodmat) + '</code>' : ''}</td>
|
||||
<td class="text-end">1</td><td class="text-end">${fmtNum(order.delivery_cost)}</td>
|
||||
<td class="text-end">${tVat}</td><td class="text-end">${fmtNum(order.delivery_cost)}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
// Discount rows (split by VAT rate)
|
||||
if (order.discount_total > 0) {
|
||||
const dCodmat = order.discount_codmat || '';
|
||||
const discSplit = computeDiscountSplit(items, order);
|
||||
if (discSplit) {
|
||||
Object.entries(discSplit)
|
||||
.sort(([a], [b]) => Number(a) - Number(b))
|
||||
.forEach(([rate, amt]) => {
|
||||
if (amt > 0) tableHtml += `<tr class="table-light">
|
||||
<td></td><td class="text-muted">Discount</td>
|
||||
<td>${dCodmat ? '<code>' + esc(dCodmat) + '</code>' : ''}</td>
|
||||
<td class="text-end">\u20131</td><td class="text-end">${fmtNum(amt)}</td>
|
||||
<td class="text-end">${Number(rate)}</td><td class="text-end">\u2013${fmtNum(amt)}</td>
|
||||
</tr>`;
|
||||
});
|
||||
} else {
|
||||
tableHtml += `<tr class="table-light">
|
||||
<td></td><td class="text-muted">Discount</td>
|
||||
<td>${dCodmat ? '<code>' + esc(dCodmat) + '</code>' : ''}</td>
|
||||
<td class="text-end">\u20131</td><td class="text-end">${fmtNum(order.discount_total)}</td>
|
||||
<td class="text-end">-</td><td class="text-end">\u2013${fmtNum(order.discount_total)}</td>
|
||||
</tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('detailItemsBody').innerHTML = tableHtml;
|
||||
_renderReceipt(items, order);
|
||||
|
||||
if (opts.onAfterRender) opts.onAfterRender(order, items);
|
||||
} catch (err) {
|
||||
document.getElementById('detailError').textContent = err.message;
|
||||
document.getElementById('detailError').style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Global quick map dispatcher — set by each page
|
||||
let _sharedModalQuickMapFn = null;
|
||||
function _sharedModalQuickMap(sku, productName, orderNumber, itemIdx) {
|
||||
if (_sharedModalQuickMapFn) _sharedModalQuickMapFn(sku, productName, orderNumber, itemIdx);
|
||||
}
|
||||
|
||||
// ── Dot helper ────────────────────────────────────
|
||||
function statusDot(status) {
|
||||
switch ((status || '').toUpperCase()) {
|
||||
|
||||
@@ -59,9 +59,64 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Shared Order Detail Modal -->
|
||||
<div class="modal fade" id="orderDetailModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Comanda <code id="detailOrderNumber"></code></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<small class="text-muted">Client:</small> <strong id="detailCustomer"></strong><br>
|
||||
<small class="text-muted">Data comanda:</small> <span id="detailDate"></span><br>
|
||||
<small class="text-muted">Status:</small> <span id="detailStatus"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<small class="text-muted">ID Comanda ROA:</small> <span id="detailIdComanda">-</span><br>
|
||||
<small class="text-muted">ID Partener:</small> <span id="detailIdPartener">-</span><br>
|
||||
<small class="text-muted">ID Adr. Facturare:</small> <span id="detailIdAdresaFact">-</span><br>
|
||||
<small class="text-muted">ID Adr. Livrare:</small> <span id="detailIdAdresaLivr">-</span>
|
||||
<div id="detailInvoiceInfo" style="display:none; margin-top:4px;">
|
||||
<small class="text-muted">Factura:</small> <span id="detailInvoiceNumber"></span>
|
||||
<span class="ms-2"><small class="text-muted">din</small> <span id="detailInvoiceDate"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive d-none d-md-block">
|
||||
<table class="table table-sm table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>SKU</th>
|
||||
<th>Produs</th>
|
||||
<th>CODMAT</th>
|
||||
<th class="text-end">Cant.</th>
|
||||
<th class="text-end">Pret</th>
|
||||
<th class="text-end">TVA%</th>
|
||||
<th class="text-end">Valoare</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="detailItemsBody">
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="detailReceipt" class="d-flex flex-wrap gap-2 mt-1 justify-content-end"></div>
|
||||
</div>
|
||||
<div class="d-md-none" id="detailItemsMobile"></div>
|
||||
<div id="detailReceiptMobile" class="d-flex flex-wrap gap-2 mt-1 d-md-none justify-content-end"></div>
|
||||
<div id="detailError" class="alert alert-danger mt-3" style="display:none;"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Inchide</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="{{ rp }}/static/js/shared.js?v=12"></script>
|
||||
<script src="{{ rp }}/static/js/shared.js?v=13"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -109,64 +109,8 @@
|
||||
<div id="dashPagination" class="pag-strip pag-strip-bottom"></div>
|
||||
</div>
|
||||
|
||||
<!-- Order Detail Modal -->
|
||||
<div class="modal fade" id="orderDetailModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Comanda <code id="detailOrderNumber"></code></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<small class="text-muted">Client:</small> <strong id="detailCustomer"></strong><br>
|
||||
<small class="text-muted">Data comanda:</small> <span id="detailDate"></span><br>
|
||||
<small class="text-muted">Status:</small> <span id="detailStatus"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<small class="text-muted">ID Comanda ROA:</small> <span id="detailIdComanda">-</span><br>
|
||||
<small class="text-muted">ID Partener:</small> <span id="detailIdPartener">-</span><br>
|
||||
<small class="text-muted">ID Adr. Facturare:</small> <span id="detailIdAdresaFact">-</span><br>
|
||||
<small class="text-muted">ID Adr. Livrare:</small> <span id="detailIdAdresaLivr">-</span>
|
||||
<div id="detailInvoiceInfo" style="display:none; margin-top:4px;">
|
||||
<small class="text-muted">Factura:</small> <span id="detailInvoiceNumber"></span>
|
||||
<span class="ms-2"><small class="text-muted">din</small> <span id="detailInvoiceDate"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive d-none d-md-block">
|
||||
<table class="table table-sm table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>SKU</th>
|
||||
<th>Produs</th>
|
||||
<th>CODMAT</th>
|
||||
<th class="text-end">Cant.</th>
|
||||
<th class="text-end">Pret</th>
|
||||
<th class="text-end">TVA%</th>
|
||||
<th class="text-end">Valoare</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="detailItemsBody">
|
||||
</tbody>
|
||||
</table>
|
||||
<div id="detailReceipt" class="d-flex flex-wrap gap-2 mt-1 justify-content-end"></div>
|
||||
</div>
|
||||
<div class="d-md-none" id="detailItemsMobile"></div>
|
||||
<div id="detailReceiptMobile" class="d-flex flex-wrap gap-2 mt-1 d-md-none justify-content-end"></div>
|
||||
<div id="detailError" class="alert alert-danger mt-3" style="display:none;"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Inchide</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Map Modal (used from order detail) -->
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=25"></script>
|
||||
<script src="{{ request.scope.get('root_path', '') }}/static/js/dashboard.js?v=26"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -96,65 +96,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Order Detail Modal -->
|
||||
<div class="modal fade" id="orderDetailModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Comanda <code id="detailOrderNumber"></code></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<small class="text-muted">Client:</small> <strong id="detailCustomer"></strong><br>
|
||||
<small class="text-muted">Data comanda:</small> <span id="detailDate"></span><br>
|
||||
<small class="text-muted">Status:</small> <span id="detailStatus"></span>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<small class="text-muted">ID Comanda ROA:</small> <span id="detailIdComanda">-</span><br>
|
||||
<small class="text-muted">ID Partener:</small> <span id="detailIdPartener">-</span><br>
|
||||
<small class="text-muted">ID Adr. Facturare:</small> <span id="detailIdAdresaFact">-</span><br>
|
||||
<small class="text-muted">ID Adr. Livrare:</small> <span id="detailIdAdresaLivr">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="detailTotals" class="d-flex gap-3 mb-2 flex-wrap" style="font-size:0.875rem">
|
||||
<span><small class="text-muted">Valoare:</small> <strong id="detailItemsTotal">-</strong></span>
|
||||
<span id="detailDiscountWrap"><small class="text-muted">Discount:</small> <strong id="detailDiscount">-</strong></span>
|
||||
<span id="detailDeliveryWrap"><small class="text-muted">Transport:</small> <strong id="detailDeliveryCost">-</strong></span>
|
||||
<span><small class="text-muted">Total:</small> <strong id="detailOrderTotal">-</strong></span>
|
||||
</div>
|
||||
<div class="table-responsive d-none d-md-block">
|
||||
<table class="table table-sm table-bordered mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>SKU</th>
|
||||
<th>Produs</th>
|
||||
<th>CODMAT</th>
|
||||
<th>Cant.</th>
|
||||
<th>Pret</th>
|
||||
<th class="text-end">Valoare</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="detailItemsBody">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="d-md-none" id="detailItemsMobile"></div>
|
||||
<div id="detailError" class="alert alert-danger mt-3" style="display:none;"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Inchide</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Map Modal (used from order detail) -->
|
||||
<!-- Hidden field for pre-selected run from URL/server -->
|
||||
<input type="hidden" id="preselectedRun" value="{{ selected_run }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script src="{{ request.scope.get('root_path', '') }}/static/js/logs.js?v=11"></script>
|
||||
<script src="{{ request.scope.get('root_path', '') }}/static/js/logs.js?v=12"></script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user