feat(safety): price comparison on order detail

Add ROA price comparison to order detail modal — operators can now see
if GoMag prices match Oracle before invoicing. Eliminates the #1 risk
of invoicing with wrong prices.

Backend:
- New get_prices_for_order() in validation_service.py — batch Oracle
  query with dual-policy routing (sales/production by cont 341/345),
  PRETURI_CU_TVA handling, kit total calculation
- Extend GET /api/sync/order/{orderNumber} with per-item pret_roa and
  order-level price_check summary
- GET /api/dashboard/orders returns price_match=null (lightweight)

Frontend:
- Modal: price check badge (green/red/grey), "Pret GoMag" + "Pret ROA"
  columns, match dot per row, mismatch rows highlighted
- Dashboard: price dot column (₽) in orders table
- Mobile: inline mismatch indicator

Cache-bust: shared.js?v=16, dashboard.js?v=28

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-03-27 12:25:02 +00:00
parent f6b6b863bd
commit 3bd0556f73
7 changed files with 280 additions and 22 deletions

View File

@@ -305,7 +305,7 @@ async function loadDashOrders() {
const orders = data.orders || [];
if (orders.length === 0) {
tbody.innerHTML = '<tr><td colspan="9" class="text-center text-muted py-3">Nicio comanda</td></tr>';
tbody.innerHTML = '<tr><td colspan="10" class="text-center text-muted py-3">Nicio comanda</td></tr>';
} else {
tbody.innerHTML = orders.map(o => {
const dateStr = fmtDate(o.order_date);
@@ -321,6 +321,7 @@ async function loadDashOrders() {
<td class="text-end text-muted">${fmtCost(o.discount_total)}</td>
<td class="text-end fw-bold">${orderTotal}</td>
<td class="text-center">${invoiceDot(o)}</td>
<td class="text-center">${priceDot(o)}</td>
</tr>`;
}).join('');
}
@@ -340,11 +341,12 @@ async function loadDashOrders() {
}
const name = o.customer_name || o.shipping_name || o.billing_name || '\u2014';
const totalStr = o.order_total ? Number(o.order_total).toFixed(2) : '';
const priceMismatch = o.price_match === false ? '<span class="dot dot-red" style="width:6px;height:6px" title="Pret!="></span> ' : '';
return `<div class="flat-row" onclick="openDashOrderDetail('${esc(o.order_number)}')" style="font-size:0.875rem">
${statusDot(o.status)}
<span style="color:var(--text-muted)" class="text-nowrap">${dateFmt}</span>
<span class="grow truncate fw-bold">${esc(name)}</span>
<span class="text-nowrap">x${o.items_count || 0}${totalStr ? ' · <strong>' + totalStr + '</strong>' : ''}</span>
<span class="text-nowrap">x${o.items_count || 0}${totalStr ? ' · ' + priceMismatch + '<strong>' + totalStr + '</strong>' : ''}</span>
</div>`;
}).join('');
}
@@ -442,6 +444,13 @@ function statusLabelText(status) {
}
}
function priceDot(order) {
if (order.price_match === true) return '<span class="dot dot-green" title="Preturi OK"></span>';
if (order.price_match === false) return '<span class="dot dot-red" title="Diferenta de pret"></span>';
if (order.price_match === null) return '<span class="dot dot-gray" title="Preturi ROA indisponibile"></span>';
return '';
}
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>';