From 1b2b1d8b24eef4f44740c45ea3dfdc291f3fc866 Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Fri, 27 Mar 2026 12:30:21 +0000 Subject: [PATCH] feat(safety): invoice reconciliation on order detail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add invoice total comparison in the order detail modal. When an order has been invoiced, shows whether the invoice total matches the order total — green badge if OK, red badge with difference amount if not. Backend: compute reconciliation (difference, match) from existing invoice.total_cu_tva vs order_total in order_detail endpoint. Frontend: reconciliation badge below invoice info in modal, hidden when no invoice exists. Cache-bust: shared.js?v=17 Co-Authored-By: Claude Opus 4.6 (1M context) --- api/app/routers/sync.py | 13 +++++++++++++ api/app/static/js/shared.js | 18 ++++++++++++++++++ api/app/templates/base.html | 3 ++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/api/app/routers/sync.py b/api/app/routers/sync.py index 09f0d7e..d01521d 100644 --- a/api/app/routers/sync.py +++ b/api/app/routers/sync.py @@ -456,6 +456,19 @@ async def order_detail(order_number: str): except Exception: pass + # Invoice reconciliation + inv = order.get("invoice") + if inv and inv.get("facturat") and inv.get("total_cu_tva") is not None: + order_total = float(order.get("order_total") or 0) + inv_total = float(inv["total_cu_tva"]) + difference = round(inv_total - order_total, 2) + inv["reconciliation"] = { + "order_total": order_total, + "invoice_total": inv_total, + "difference": difference, + "match": abs(difference) < 0.01, + } + # Parse discount_split JSON string if order.get("discount_split"): try: diff --git a/api/app/static/js/shared.js b/api/app/static/js/shared.js index 09177e5..b150c09 100644 --- a/api/app/static/js/shared.js +++ b/api/app/static/js/shared.js @@ -481,6 +481,8 @@ async function renderOrderDetailModal(orderNumber, opts) { if (mobileContainer) mobileContainer.innerHTML = ''; const priceCheckEl = document.getElementById('detailPriceCheck'); if (priceCheckEl) priceCheckEl.innerHTML = ''; + const reconEl = document.getElementById('detailInvoiceRecon'); + if (reconEl) { reconEl.innerHTML = ''; reconEl.style.display = 'none'; } const modalEl = document.getElementById('orderDetailModal'); const existing = bootstrap.Modal.getInstance(modalEl); @@ -529,6 +531,22 @@ async function renderOrderDetailModal(orderNumber, opts) { if (invInfo) invInfo.style.display = ''; } + // Invoice reconciliation + const reconEl = document.getElementById('detailInvoiceRecon'); + if (reconEl && inv && inv.reconciliation) { + const r = inv.reconciliation; + if (r.match) { + reconEl.innerHTML = `✓ Total factura OK (${fmtNum(r.invoice_total)} lei)`; + } else { + const sign = r.difference > 0 ? '+' : ''; + reconEl.innerHTML = `Diferenta: ${sign}${fmtNum(r.difference)} lei + Factura: ${fmtNum(r.invoice_total)} | Comanda: ${fmtNum(r.order_total)}`; + } + reconEl.style.display = ''; + } else if (reconEl) { + reconEl.style.display = 'none'; + } + if (order.error_message) { document.getElementById('detailError').textContent = order.error_message; document.getElementById('detailError').style.display = ''; diff --git a/api/app/templates/base.html b/api/app/templates/base.html index 46dba28..9ce6002 100644 --- a/api/app/templates/base.html +++ b/api/app/templates/base.html @@ -106,6 +106,7 @@ @@ -142,7 +143,7 @@ - +