File: D:/web/hyperflight/apps/inxpress/app/templates/shipments_parsed - Copy (3).html
{% extends "base.html" %}
{% block title %}InXpress — Shipments{% endblock %}
{% block content %}
{% macro sort_link(label, col) -%}
{%- set newdir = 'asc' if sort != col or direction=='desc' else 'desc' -%}
<a class="link-body-emphasis text-decoration-none"
href="{{ url_for('shipments_parsed', q=q, carrier=carrier, from=date_from, to=date_to,
pagesize=pagesize, page=1, sort=col, dir=newdir, duponly=1 if duponly else None) }}">
{{ label }}
{% if sort == col %}<span class="small">{{ '▲' if direction=='asc' else '▼' }}</span>{% endif %}
</a>
{%- endmacro %}
{% macro ref_badge(val) -%}
{% set raw = (val or '') %}
{% set key = raw|norm_ref %}
{% if key and duplicates.get(key, 0) > 1 %}
<span class="badge bg-warning-subtle text-warning-emphasis"
title="Duplicate reference ({{ duplicates[key] }} occurrences)">
{{ raw }}
</span>
{% else %}
{{ raw }}
{% endif %}
{%- endmacro %}
<div class="d-flex align-items-center justify-content-between mb-3">
<h1 class="h4 mb-0">Shipments</h1>
<div class="text-muted small">Total: {{ total }}</div>
</div>
<form method="get"
action="{{ url_for('shipments_parsed') }}"
class="row row-cols-1 row-cols-lg-auto g-2 align-items-end mb-3">
<input type="hidden" name="page" value="{{ page }}">
<div class="col-auto">
<label for="q" class="form-label mb-0">Search</label>
<input id="q" name="q" type="text"
class="form-control search-narrow"
value="{{ q or '' }}"
placeholder="Ref / name / airbill"
onkeydown="if(event.key==='Enter'){ this.form.page.value=1; }">
</div>
<div class="col">
<label class="form-label">Invoice</label>
<select name="invoice" class="form-select"
onchange="this.form.page.value=1; this.form.submit()">
<option value="">All invoices</option>
{% for inv in invoices %}
<option value="{{ inv }}" {{ 'selected' if invoice==inv else '' }}>{{ inv }}</option>
{% endfor %}
</select>
</div>
<div class="col">
<label class="form-label">Carrier</label>
<select name="carrier" class="form-select"
onchange="this.form.page.value=1; this.form.submit()">
<option value="">All carriers</option>
{% for c in carriers %}
<option value="{{ c }}" {{ 'selected' if carrier==c else '' }}>{{ c }}</option>
{% endfor %}
</select>
</div>
<div class="col">
<label class="form-label">Country</label>
<select name="country" class="form-select" style="width:14rem"
onchange="this.form.page.value=1; this.form.submit()">
<option value="">All countries</option>
{% for c in countries %}
<option value="{{ c.cc }}" {{ 'selected' if country==c.cc else '' }}>
{{ c.name }} ({{ c.cc }})
</option>
{% endfor %}
</select>
</div>
<div class="col">
<label class="form-label">From</label>
<input type="date" name="from" class="form-control" lang="en-GB"
value="{{ date_from_iso or '' }}"
onchange="this.form.page.value=1; this.form.submit()">
</div>
<div class="col">
<label class="form-label">To</label>
<input type="date" name="to" class="form-control" lang="en-GB"
value="{{ date_to_iso or '' }}"
onchange="this.form.page.value=1; this.form.submit()">
</div>
<div class="col">
<label class="form-label">Page size</label>
<select name="pagesize" class="form-select" style="width:8rem"
onchange="this.form.page.value=1; this.form.submit()">
<option value="25" {{ 'selected' if pagesize_raw=='25' else '' }}>25</option>
<option value="50" {{ 'selected' if pagesize_raw=='50' else '' }}>50</option>
<option value="100" {{ 'selected' if pagesize_raw=='100' else '' }}>100</option>
<option value="200" {{ 'selected' if pagesize_raw in ['200',''] else '' }}>200</option>
<option value="all" {{ 'selected' if pagesize_raw=='all' else '' }}>All records</option>
</select>
</div>
<!-- Duplicates + No matching order stacked -->
<div class="col-auto">
<div class="d-flex flex-column">
<div class="form-check mb-1">
<input class="form-check-input" type="checkbox" id="duponly" name="duponly"
value="1" {{ 'checked' if duponly else '' }}
onchange="this.form.page.value=1; this.form.submit()">
<label class="form-check-label" for="duponly">Duplicates only</label>
</div>
<div class="form-check">
<!-- hidden default so param posts when unticked -->
<input type="hidden" name="noorder" value="0">
<input class="form-check-input" type="checkbox" id="noorder" name="noorder"
value="1" {{ 'checked' if noorder else '' }}
onchange="this.form.page.value=1; this.form.submit()">
<label class="form-check-label" for="noorder">No matching order</label>
</div>
</div>
</div>
<div class="col">
<button type="submit" class="btn btn-primary">Apply</button>
{% if q or invoice or carrier or country or date_from_iso or date_to_iso
or (pagesize_raw and pagesize_raw != '50') %}
<a class="btn btn-outline-secondary ms-1" href="{{ url_for('shipments_parsed') }}">Clear</a>
{% endif %}
</div>
</form>
<div class="table-responsive">
<table class="table table-sm table-striped align-middle">
<thead class="table-light">
<tr>
<th class="text-end">{{ sort_link('ID','shipment_id') }}</th>
<th>{{ sort_link('Date','ShipDate') }}</th>
<th>{{ sort_link('Carrier','Carrier') }}</th>
<th>{{ sort_link('Service','ServiceType') }}</th>
<th>{{ sort_link('Country','DestCountry') }}</th> {# still sorts by destination #}
<th class="text-center">{{ sort_link('Zone','Zone') }}</th> <!-- centered -->
<th>{{ sort_link('Refs','Reference') }}</th> <!-- moved here -->
<th>{{ sort_link('Receiver','ReceiverName') }}</th> <!-- moved here -->
<th class="text-end">{{ sort_link('Pcs','Pieces') }}</th>
<th class="text-end">{{ sort_link('Weight','WeightKg') }}</th>
<th class="text-end">{{ sort_link('L × W × H','Length') }}</th>
<th class="text-end">{{ sort_link('Base (£)','BaseCharge') }}</th>
<th class="text-end">{{ sort_link('Fuel (£)','FuelSurcharge') }}</th>
<th class="text-end">Other (£)</th>
<th class="text-end">{{ sort_link('Net (£)','NetAmount') }}</th>
<th class="text-end">{{ sort_link('VAT (£)','VATAmount') }}</th>
<th class="text-end">{{ sort_link('Total (£)','Total') }}</th>
<th class="text-end">{{ sort_link('Charge (£)', 'Charge') }}</th>
<th class="text-end">{{ sort_link('Cost (£)', 'Cost') }}</th>
<th class="text-end">{{ sort_link('Calc (£)', 'Calculated') }}</th>
<th class="text-end">{{ sort_link('Cost Δ', 'DiffCost') }}</th>
<th class="text-end">{{ sort_link('Cost Δ %', 'DiffCostPct') }}</th>
<th class="text-end">{{ sort_link('Calc Δ', 'DiffCalc') }}</th>
<th class="text-end">{{ sort_link('Calc Δ %', 'DiffCalcPct') }}</th>
</tr>
</thead>
<tbody>
{% for r in rows %}
<tr>
<td class="text-end">
<a
href="{{ url_for('shipment_detail', shipment_id=r.shipment_id) }}"
class="open-shipment"
data-id="{{ r.shipment_id }}"
data-invoice="{{ r.invoice_number or '' }}"
onclick="return openShipmentModal(this);"
>{{ r.shipment_id }}</a>
</td>
<td class="text-nowrap">{{ r.ShipDate|fmt_date }}</td>
<td class="text-nowrap">{{ r.Carrier }}</td>
<!--<td class="text-truncate" style="max-width: 240px;">{{ r.ServiceType }}</td>-->
<td class="align-middle"><div class="small text-tiny">{{ r.ServiceType }}</div></td>
{# Country cell: default DEST; if ORIGIN != GB, show ORIGIN with small back arrow #}
{% set oc = (r.OriginCountry or '')|upper %}
{% set dc = (r.DestCountry or '')|upper %}
{% set use_origin = oc and oc != 'GB' %}
<td class="text-nowrap align-middle">
{% if use_origin %}
<span class="me-1 text-muted small" title="Origin country shown">↩︎</span>
<img src="{{ url_for('static', filename='flags/' ~ (oc|lower if oc else 'globe') ~ '.svg') }}"
width="24" height="18" alt="{{ oc or 'globe' }}" class="flag-img">
<span class="cc">{{ oc }}</span>
{% else %}
<img src="{{ url_for('static', filename='flags/' ~ (dc|lower if dc else 'globe') ~ '.svg') }}"
width="24" height="18" alt="{{ dc or 'globe' }}" class="flag-img">
<span class="cc">{{ dc }}</span>
{% endif %}
</td>
<td class="text-center">{{ r.Zone }}</td> <!-- centered -->
<td class="text-truncate" style="max-width: 300px;">
{% if r.Reference %}
<a href="#" class="open-order" data-ref="{{ r.Reference }}">{{ ref_badge(r.Reference) }}</a>
{% endif %}
{% if r.Reference2 %} · {{ ref_badge(r.Reference2) }}{% endif %}{% if r.Reference3 %} · {{ ref_badge(r.Reference3) }}{% endif %}
</td>
<!--<td class="text-truncate" style="max-width: 260px;">{{ r.ReceiverName }}</td>-->
<td class="align-middle"><div class="small text-tiny">{{ r.ReceiverName }}</div></td>
<td class="text-end">{{ r.Pieces }}</td>
<td class="text-end">{{ r.WeightKg|trim2 }}</td>
<td class="text-end">
{% set dims = [r.Length, r.Width, r.Height] %}
{% if dims[0] or dims[1] or dims[2] %}
{{ dims[0]|trim2 }}×{{ dims[1]|trim2 }}×{{ dims[2]|trim2 }}
{% endif %}
</td>
<td class="text-end">{{ r.BaseCharge|blank_if_zero }}</td>
<td class="text-end">{{ r.FuelSurcharge|blank_if_zero }}</td>
<td class="text-end">{{ r.Other|blank_if_zero }}</td>
<td class="text-end fw-semibold">{{ r.NetAmount|fmt }}</td>
<td class="text-end">{{ r.VATAmount|fmt }}</td>
<td class="text-end fw-semibold">{{ r.Total|fmt }}</td>
{# value delta: signed colour, ASCII only #}
{% macro diff_val(v) -%}
{% if v is not none %}
{% set cls = 'text-success' if v < 0 else ('text-danger' if v > 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%.2f'|format(v) }}</span>
{% endif %}
{%- endmacro %}
{# percent delta: signed with + for positives, ASCII only #}
{% macro diff_pct(p) -%}
{% if p is not none %}
{% set cls = 'text-success' if p < 0 else ('text-danger' if p > 0 else 'text-body') %}
{# format with sign, then add a small space after + or - using filter chaining #}
<span class="{{ cls }}">
{{ '%+0.1f%%'|format(p)|replace('+', '+ ')|replace('-', '- ') }}
</span>
{% endif %}
{%- endmacro %}
{# values #}
<td class="text-end">{{ r.Charge|blank_if_zero }}</td>
<td class="text-end">{{ r.Cost|blank_if_zero }}</td>
<td class="text-end">{{ r.Calculated|blank_if_zero }}</td>
{# deltas: value + percent #}
<td class="text-end">{{ diff_val(r.DiffCost) }}</td>
<td class="text-end">{{ diff_pct(r.DiffCostPct) }}</td>
<td class="text-end">{{ diff_val(r.DiffCalc) }}</td>
<td class="text-end">{{ diff_pct(r.DiffCalcPct) }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr class="table-secondary">
<td colspan="11" class="text-end fw-semibold">Page totals:</td>
<td class="text-end">{{ sums.BaseCharge|blank_if_zero }}</td>
<td class="text-end">{{ sums.FuelSurcharge|blank_if_zero }}</td>
<td class="text-end">{{ sums.Other|blank_if_zero }}</td>
<td class="text-end fw-semibold">{{ sums.NetAmount|fmt }}</td>
<td class="text-end">{{ sums.VATAmount|fmt }}</td>
<td class="text-end fw-semibold">{{ sums.Total|fmt }}</td>
{# …existing footer cells for other totals… #}
{# Charge / Cost / Calculated totals #}
<td class="text-end fw-semibold">{{ sums.Charge | fmt }}</td>
<td class="text-end fw-semibold">{{ sums.Cost | fmt }}</td>
<td class="text-end fw-semibold">{{ sums.Calculated | fmt }}</td>
{# Δ vs Net (value + %) – value uses same coloring rule as rows #}
{% set dc = sums.DiffCostTotal %}
<td class="text-end fw-semibold">
{% if dc is not none %}
{% set cls = 'text-success' if dc < 0 else ('text-danger' if dc > 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%.2f'|format(dc) }}</span>
{% endif %}
</td>
{% set dcp = sums.DiffCostPct %}
<td class="text-end fw-semibold">
{% if dcp is not none %}
{% set cls = 'text-success' if dcp < 0 else ('text-danger' if dcp > 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%+0.1f%%'|format(dcp) }}</span>
{% endif %}
</td>
{% set dl = sums.DiffCalcTotal %}
<td class="text-end fw-semibold">
{% if dl is not none %}
{% set cls = 'text-success' if dl < 0 else ('text-danger' if dl > 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%.2f'|format(dl) }}</span>
{% endif %}
</td>
{% set dlp = sums.DiffCalcPct %}
<td class="text-end fw-semibold">
{% if dlp is not none %}
{% set cls = 'text-success' if dlp < 0 else ('text-danger' if dlp > 0 else 'text-body') %}
<span class="{{ cls }}">{{ '%+0.1f%%'|format(dlp) }}</span>
{% endif %}
</td>
</tr>
</tfoot>
</table>
</div>
<!-- Modal -->
<div class="modal fade" id="shipmentModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Shipment details</h5>
<button type="button"
class="btn btn-outline-secondary btn-sm"
data-bs-dismiss="modal"
aria-label="Close"
title="Close (Esc)">
Close
</button>
</div>
<div class="modal-body">
<div class="text-center py-5" id="shipmentModalLoading">
<div class="spinner-border" role="status" aria-hidden="true"></div>
<div class="mt-2 small text-muted">Loading…</div>
</div>
<div id="shipmentModalBody" class="d-none"></div>
</div>
</div>
</div>
</div>
<script>
function openShipmentModal(anchor){
try {
const modalEl = document.getElementById('shipmentModal');
const bodyEl = document.getElementById('shipmentModalBody');
const loadEl = document.getElementById('shipmentModalLoading');
if (!modalEl || !bodyEl || !loadEl || !window.bootstrap) return true; // fall back to navigation
const id = anchor.getAttribute('data-id') || '';
const inv = anchor.getAttribute('data-invoice') || '';
const url = anchor.getAttribute('href');
// Title + reset UI
modalEl.querySelector('.modal-title').textContent =
'Shipment #' + id + (inv ? ' · ' + inv : '');
loadEl.classList.remove('d-none');
bodyEl.classList.add('d-none');
bodyEl.innerHTML = '';
// Show modal
new bootstrap.Modal(modalEl).show();
// Load content
fetch(url, { cache: 'no-store', headers: { 'X-Requested-With': 'XMLHttpRequest' } })
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
.then(html => { bodyEl.innerHTML = html; loadEl.classList.add('d-none'); bodyEl.classList.remove('d-none'); })
.catch(err => { bodyEl.innerHTML = '<div class="alert alert-danger small mb-0">Could not load shipment ' + id + ': ' + err.message + '</div>'; loadEl.classList.add('d-none'); bodyEl.classList.remove('d-none'); });
return false; // PREVENT NAVIGATION
} catch(e) {
console.error(e);
return true; // fallback to opening the link normally
}
}
</script>
<div class="modal fade" id="orderModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-lg">
<div class="modal-content">
<div class="modal-header py-2">
<h5 class="modal-title">Order</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Loading…
</div>
</div>
</div>
</div>
<script>
document.addEventListener('click', function (e) {
const link = e.target.closest('.open-order');
if (!link) return;
e.preventDefault();
const ref = link.dataset.ref;
const modalEl = document.getElementById('orderModal');
const body = modalEl.querySelector('.modal-body');
body.textContent = 'Loading…';
const url = '{{ url_for("order_by_ref", ref="__REF__") }}'.replace('__REF__', encodeURIComponent(ref));
fetch(url, { headers: { 'X-Requested-With': 'fetch' }})
.then(r => r.text())
.then(html => {
body.innerHTML = html;
bootstrap.Modal.getOrCreateInstance(modalEl).show();
})
.catch(() => { body.innerHTML = '<div class="text-danger">Error loading order.</div>'; });
});
</script>
{% if last_page > 1 %}
<nav class="mt-3" aria-label="Shipments pagination">
{% set prevp = 1 if page<=1 else page-1 %}
{% set nextp = last_page if page>=last_page else page+1 %}
<ul class="pagination pagination-sm mb-0">
<li class="page-item {{ 'disabled' if page==1 }}">
<a class="page-link" href="{{ url_for('shipments_parsed', q=q, carrier=carrier, from=date_from, to=date_to, pagesize=pagesize, duponly=1 if duponly else None, noorder=1 if noorder else None, page=1) }}">« First</a>
</li>
<li class="page-item {{ 'disabled' if page==1 }}">
<a class="page-link" href="{{ url_for('shipments_parsed', q=q, carrier=carrier, from=date_from, to=date_to, pagesize=pagesize, duponly=1 if duponly else None, noorder=1 if noorder else None, page=prevp) }}">‹ Prev</a>
</li>
<li class="page-item disabled"><span class="page-link">Page {{ page }} / {{ last_page }}</span></li>
<li class="page-item {{ 'disabled' if page==last_page }}">
<a class="page-link" href="{{ url_for('shipments_parsed', q=q, carrier=carrier, from=date_from, to=date_to, pagesize=pagesize,duponly=1 if duponly else None, noorder=1 if noorder else None, page=nextp) }}">Next ›</a>
</li>
<li class="page-item {{ 'disabled' if page==last_page }}">
<a class="page-link" href="{{ url_for('shipments_parsed', q=q, carrier=carrier, from=date_from, to=date_to, pagesize=pagesize, duponly=1 if duponly else None, noorder=1 if noorder else None, page=last_page) }}">Last »</a>
</li>
</ul>
</nav>
{% endif %}
{% endblock %}