diff --git a/api/app/static/js/dashboard.js b/api/app/static/js/dashboard.js
index 6415752..5e47c61 100644
--- a/api/app/static/js/dashboard.js
+++ b/api/app/static/js/dashboard.js
@@ -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 'Importat';
- case 'ALREADY_IMPORTED': return 'Deja importat';
- case 'SKIPPED': return 'Omis';
- case 'ERROR': return 'Eroare';
- case 'CANCELLED': return 'Anulat';
- case 'DELETED_IN_ROA': return 'Sters din ROA';
- default: return `${esc(status)}`;
- }
-}
-
function invoiceDot(order) {
if (order.status !== 'IMPORTED' && order.status !== 'ALREADY_IMPORTED') return '–';
if (order.invoice && order.invoice.facturat) return '';
return '';
}
-function renderCodmatCell(item) {
- if (!item.codmat_details || item.codmat_details.length === 0) {
- return `${esc(item.codmat || '-')}`;
- }
- if (item.codmat_details.length === 1) {
- const d = item.codmat_details[0];
- if (d.direct) {
- return `${esc(d.codmat)} direct`;
- }
- return `${esc(d.codmat)}`;
- }
- return item.codmat_details.map(d =>
- `
${esc(d.codmat)} \xd7${d.cantitate_roa}
`
- ).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 = '| Se incarca... |
';
- 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 = '| Niciun articol |
';
- 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 => `${esc(d.codmat)}${d.direct ? ' direct' : ''}`).join(' ')
- : `${esc(item.codmat || '–')}`;
- const valoare = (Number(item.price || 0) * Number(item.quantity || 0));
- return `
-
- ${esc(item.sku)}
- ${codmatText}
-
-
- ${esc(item.product_name || '–')}
- x${item.quantity || 0}
- ${fmtNum(valoare)} lei
- TVA ${item.vat != null ? Number(item.vat) : '?'}
-
-
`;
- }).join('');
-
- // Transport row (mobile)
- if (order.delivery_cost > 0) {
- const tVat = order.transport_vat || '21';
- mobileHtml += `
-
- Transport
- x1
- ${fmtNum(order.delivery_cost)} lei
- TVA ${tVat}
-
-
`;
- }
-
- // 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 += `
-
- Discount
- x\u20131
- ${fmtNum(amt)} lei
- TVA ${Number(rate)}
-
-
`;
- });
- } else {
- mobileHtml += `
-
- Discount
- x\u20131
- ${fmtNum(order.discount_total)} lei
-
-
`;
- }
- }
-
- mobileContainer.innerHTML = '' + mobileHtml + '
';
- }
-
- let tableHtml = items.map((item, idx) => {
- const valoare = Number(item.price || 0) * Number(item.quantity || 0);
- return `
- ${esc(item.sku)} |
- ${esc(item.product_name || '-')} |
- ${renderCodmatCell(item)} |
- ${item.quantity || 0} |
- ${item.price != null ? fmtNum(item.price) : '-'} |
- ${item.vat != null ? Number(item.vat) : '-'} |
- ${fmtNum(valoare)} |
-
`;
- }).join('');
-
- // Transport row
- if (order.delivery_cost > 0) {
- const tVat = order.transport_vat || '21';
- const tCodmat = order.transport_codmat || '';
- tableHtml += `
- | Transport |
- ${tCodmat ? '' + esc(tCodmat) + '' : ''} |
- 1 | ${fmtNum(order.delivery_cost)} |
- ${tVat} | ${fmtNum(order.delivery_cost)} |
-
`;
- }
-
- // 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 += `
- | Discount |
- ${dCodmat ? '' + esc(dCodmat) + '' : ''} |
- \u20131 | ${fmtNum(amt)} |
- ${Number(rate)} | \u2013${fmtNum(amt)} |
-
`;
- });
- } else {
- tableHtml += `
- | Discount |
- ${dCodmat ? '' + esc(dCodmat) + '' : ''} |
- \u20131 | ${fmtNum(order.discount_total)} |
- - | \u2013${fmtNum(order.discount_total)} |
-
`;
- }
- }
-
- 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 = `Articole: ${fmtNum(articole)}`;
- if (discount > 0) dHtml += `Discount: \u2013${fmtNum(discount)}`;
- if (transport > 0) dHtml += `Transport: ${fmtNum(transport)}`;
- dHtml += `Total: ${total} lei`;
- desktop.innerHTML = dHtml;
-
- // Mobile: shorter labels
- let mHtml = `Art: ${fmtNum(articole)}`;
- if (discount > 0) mHtml += `Disc: \u2013${fmtNum(discount)}`;
- if (transport > 0) mHtml += `Transp: ${fmtNum(transport)}`;
- mHtml += `Total: ${total} lei`;
- mobile.innerHTML = mHtml;
}
// ── Quick Map Modal (uses shared openQuickMap) ───
diff --git a/api/app/static/js/logs.js b/api/app/static/js/logs.js
index b70dedb..fd5eaf5 100644
--- a/api/app/static/js/logs.js
+++ b/api/app/static/js/logs.js
@@ -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 'Importat';
- case 'ALREADY_IMPORTED': return 'Deja importat';
- case 'SKIPPED': return 'Omis';
- case 'ERROR': return 'Eroare';
- case 'DELETED_IN_ROA': return 'Sters din ROA';
- default: return `${esc(status)}`;
- }
-}
-
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 `${esc(item.codmat || '-')}`;
- }
- if (item.codmat_details.length === 1) {
- const d = item.codmat_details[0];
- return `${esc(d.codmat)}`;
- }
- // Multi-CODMAT: compact list
- return item.codmat_details.map(d =>
- `${esc(d.codmat)} \xd7${d.cantitate_roa}
`
- ).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 = '| Se incarca... |
';
- 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 = '| Niciun articol |
';
- 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 = '' + items.map((item, idx) => {
- const codmatList = item.codmat_details?.length
- ? item.codmat_details.map(d => `
${esc(d.codmat)}`).join(' ')
- : `
${esc(item.codmat || '–')}`;
- const valoare = (Number(item.price || 0) * Number(item.quantity || 0)).toFixed(2);
- return `
-
- ${esc(item.sku)}
- ${codmatList}
-
-
- ${esc(item.product_name || '–')}
- x${item.quantity || 0}
- ${valoare} lei
-
-
`;
- }).join('') + '
';
- }
-
- document.getElementById('detailItemsBody').innerHTML = items.map(item => {
- const valoare = (Number(item.price || 0) * Number(item.quantity || 0)).toFixed(2);
- const codmatCell = `${renderCodmatCell(item)}`;
- return `
- ${esc(item.sku)} |
- ${esc(item.product_name || '-')} |
- ${codmatCell} |
- ${item.quantity || 0} |
- ${item.price != null ? Number(item.price).toFixed(2) : '-'} |
- ${valoare} |
-
`;
- }).join('');
- } catch (err) {
- document.getElementById('detailError').textContent = err.message;
- document.getElementById('detailError').style.display = '';
- }
+ });
}
// ── Quick Map Modal (uses shared openQuickMap) ───
diff --git a/api/app/static/js/shared.js b/api/app/static/js/shared.js
index 5486eb2..9ac20ee 100644
--- a/api/app/static/js/shared.js
+++ b/api/app/static/js/shared.js
@@ -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 'Importat';
+ case 'ALREADY_IMPORTED': return 'Deja importat';
+ case 'SKIPPED': return 'Omis';
+ case 'ERROR': return 'Eroare';
+ case 'CANCELLED': return 'Anulat';
+ case 'DELETED_IN_ROA': return 'Sters din ROA';
+ default: return `${esc(status)}`;
+ }
+}
+
+function renderCodmatCell(item) {
+ if (!item.codmat_details || item.codmat_details.length === 0) {
+ return `${esc(item.codmat || '-')}`;
+ }
+ if (item.codmat_details.length === 1) {
+ const d = item.codmat_details[0];
+ if (d.direct) {
+ return `${esc(d.codmat)} direct`;
+ }
+ return `${esc(d.codmat)}`;
+ }
+ return item.codmat_details.map(d =>
+ `${esc(d.codmat)} \xd7${d.cantitate_roa}
`
+ ).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 = `Articole: ${fmtNum(articole)}`;
+ if (discount > 0) dHtml += `Discount: \u2013${fmtNum(discount)}`;
+ if (transport > 0) dHtml += `Transport: ${fmtNum(transport)}`;
+ dHtml += `Total: ${total} lei`;
+ if (desktop) desktop.innerHTML = dHtml;
+
+ let mHtml = `Art: ${fmtNum(articole)}`;
+ if (discount > 0) mHtml += `Disc: \u2013${fmtNum(discount)}`;
+ if (transport > 0) mHtml += `Transp: ${fmtNum(transport)}`;
+ mHtml += `Total: ${total} lei`;
+ 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 = '| Se incarca... |
';
+ 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 = '| Niciun articol |
';
+ 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 => `${esc(d.codmat)}${d.direct ? ' direct' : ''}`).join(' ')
+ : `${esc(item.codmat || '–')}`;
+ 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 `
+
+ ${esc(item.sku)}
+ ${codmatText}
+
+
+ ${esc(item.product_name || '–')}
+ x${item.quantity || 0}
+ ${fmtNum(valoare)} lei
+ TVA ${item.vat != null ? Number(item.vat) : '?'}
+
+
`;
+ }).join('');
+
+ // Transport row (mobile)
+ if (order.delivery_cost > 0) {
+ const tVat = order.transport_vat || '21';
+ mobileHtml += `
+
+ Transport
+ x1
+ ${fmtNum(order.delivery_cost)} lei
+ TVA ${tVat}
+
+
`;
+ }
+
+ // 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 += `
+
+ Discount
+ x\u20131
+ ${fmtNum(amt)} lei
+ TVA ${Number(rate)}
+
+
`;
+ });
+ } else {
+ mobileHtml += `
+
+ Discount
+ x\u20131
+ ${fmtNum(order.discount_total)} lei
+
+
`;
+ }
+ }
+
+ mobileContainer.innerHTML = '' + mobileHtml + '
';
+ }
+
+ // 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 `
+ ${esc(item.sku)} |
+ ${esc(item.product_name || '-')} |
+ ${renderCodmatCell(item)} |
+ ${item.quantity || 0} |
+ ${item.price != null ? fmtNum(item.price) : '-'} |
+ ${item.vat != null ? Number(item.vat) : '-'} |
+ ${fmtNum(valoare)} |
+
`;
+ }).join('');
+
+ // Transport row
+ if (order.delivery_cost > 0) {
+ const tVat = order.transport_vat || '21';
+ const tCodmat = order.transport_codmat || '';
+ tableHtml += `
+ | Transport |
+ ${tCodmat ? '' + esc(tCodmat) + '' : ''} |
+ 1 | ${fmtNum(order.delivery_cost)} |
+ ${tVat} | ${fmtNum(order.delivery_cost)} |
+
`;
+ }
+
+ // 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 += `
+ | Discount |
+ ${dCodmat ? '' + esc(dCodmat) + '' : ''} |
+ \u20131 | ${fmtNum(amt)} |
+ ${Number(rate)} | \u2013${fmtNum(amt)} |
+
`;
+ });
+ } else {
+ tableHtml += `
+ | Discount |
+ ${dCodmat ? '' + esc(dCodmat) + '' : ''} |
+ \u20131 | ${fmtNum(order.discount_total)} |
+ - | \u2013${fmtNum(order.discount_total)} |
+
`;
+ }
+ }
+
+ 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()) {
diff --git a/api/app/templates/base.html b/api/app/templates/base.html
index be75f6b..a20ef89 100644
--- a/api/app/templates/base.html
+++ b/api/app/templates/base.html
@@ -59,9 +59,64 @@
+
+
+
+
+
+
+
+
+ Client:
+ Data comanda:
+ Status:
+
+
+
ID Comanda ROA: -
+
ID Partener: -
+
ID Adr. Facturare: -
+
ID Adr. Livrare: -
+
+ Factura:
+ din
+
+
+
+
+
+
+
+ | SKU |
+ Produs |
+ CODMAT |
+ Cant. |
+ Pret |
+ TVA% |
+ Valoare |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
{% block scripts %}{% endblock %}