feat(safety): invoice reconciliation on order detail
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) <noreply@anthropic.com>
This commit is contained in:
@@ -456,6 +456,19 @@ async def order_detail(order_number: str):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
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
|
# Parse discount_split JSON string
|
||||||
if order.get("discount_split"):
|
if order.get("discount_split"):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -481,6 +481,8 @@ async function renderOrderDetailModal(orderNumber, opts) {
|
|||||||
if (mobileContainer) mobileContainer.innerHTML = '';
|
if (mobileContainer) mobileContainer.innerHTML = '';
|
||||||
const priceCheckEl = document.getElementById('detailPriceCheck');
|
const priceCheckEl = document.getElementById('detailPriceCheck');
|
||||||
if (priceCheckEl) priceCheckEl.innerHTML = '';
|
if (priceCheckEl) priceCheckEl.innerHTML = '';
|
||||||
|
const reconEl = document.getElementById('detailInvoiceRecon');
|
||||||
|
if (reconEl) { reconEl.innerHTML = ''; reconEl.style.display = 'none'; }
|
||||||
|
|
||||||
const modalEl = document.getElementById('orderDetailModal');
|
const modalEl = document.getElementById('orderDetailModal');
|
||||||
const existing = bootstrap.Modal.getInstance(modalEl);
|
const existing = bootstrap.Modal.getInstance(modalEl);
|
||||||
@@ -529,6 +531,22 @@ async function renderOrderDetailModal(orderNumber, opts) {
|
|||||||
if (invInfo) invInfo.style.display = '';
|
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 = `<span class="badge" style="background:var(--success-light);color:var(--success-text)">✓ Total factura OK (${fmtNum(r.invoice_total)} lei)</span>`;
|
||||||
|
} else {
|
||||||
|
const sign = r.difference > 0 ? '+' : '';
|
||||||
|
reconEl.innerHTML = `<span class="badge" style="background:var(--error-light);color:var(--error-text)">Diferenta: ${sign}${fmtNum(r.difference)} lei</span>
|
||||||
|
<small class="text-muted ms-2">Factura: ${fmtNum(r.invoice_total)} | Comanda: ${fmtNum(r.order_total)}</small>`;
|
||||||
|
}
|
||||||
|
reconEl.style.display = '';
|
||||||
|
} else if (reconEl) {
|
||||||
|
reconEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
if (order.error_message) {
|
if (order.error_message) {
|
||||||
document.getElementById('detailError').textContent = order.error_message;
|
document.getElementById('detailError').textContent = order.error_message;
|
||||||
document.getElementById('detailError').style.display = '';
|
document.getElementById('detailError').style.display = '';
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
<div id="detailInvoiceInfo" style="display:none; margin-top:4px;">
|
<div id="detailInvoiceInfo" style="display:none; margin-top:4px;">
|
||||||
<small class="text-muted">Factura:</small> <span id="detailInvoiceNumber"></span>
|
<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>
|
<span class="ms-2"><small class="text-muted">din</small> <span id="detailInvoiceDate"></span></span>
|
||||||
|
<div id="detailInvoiceRecon" class="mt-1" style="display:none"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -142,7 +143,7 @@
|
|||||||
|
|
||||||
<script>window.ROOT_PATH = "{{ rp }}";</script>
|
<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="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="{{ rp }}/static/js/shared.js?v=16"></script>
|
<script src="{{ rp }}/static/js/shared.js?v=17"></script>
|
||||||
<script>
|
<script>
|
||||||
// Dark mode toggle
|
// Dark mode toggle
|
||||||
function toggleDarkMode() {
|
function toggleDarkMode() {
|
||||||
|
|||||||
Reference in New Issue
Block a user