Files
2026-07-16 19:30:24 +02:00

268 lines
11 KiB
HTML

{% extends 'base.html' %}
{% block title %}Commandes{% endblock %}
{% block content %}
<div class="page-header d-flex justify-content-between align-items-center">
<h1><i class="bi bi-printer me-2 text-primary"></i>Commandes</h1>
<div class="d-flex gap-2">
<a href="{{ url_for('export_csv') }}" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-download me-1"></i>Export CSV
</a>
<a href="{{ url_for('new_job') }}" class="btn btn-sm" style="background:#ff6b35;color:#fff">
<i class="bi bi-plus-lg me-1"></i>Nouveau
</a>
</div>
</div>
<!-- Barre de filtres -->
<div class="card mb-3">
<div class="card-body py-2 px-3">
<div class="row g-2 align-items-center">
<div class="col-auto d-flex align-items-center gap-1">
<label class="text-muted small mb-0">Du</label>
<input type="date" id="fDateFrom" class="form-control form-control-sm" style="width:140px">
</div>
<div class="col-auto d-flex align-items-center gap-1">
<label class="text-muted small mb-0">au</label>
<input type="date" id="fDateTo" class="form-control form-control-sm" style="width:140px">
</div>
<div class="col-auto">
<select id="fClient" class="form-select form-select-sm" style="min-width:160px">
<option value="">Tous les clients</option>
{% for c in clients %}
<option value="{{ c.id }}">{{ c.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-auto d-flex align-items-center gap-1">
<label class="text-muted small mb-0">Tri</label>
<select id="fSort" class="form-select form-select-sm" style="min-width:165px">
<option value="date-desc">Date (recent)</option>
<option value="date-asc">Date (ancien)</option>
<option value="prix-desc">Prix HT desc</option>
<option value="prix-asc">Prix HT asc</option>
<option value="poids-desc">Poids desc</option>
<option value="poids-asc">Poids asc</option>
<option value="duree-desc">Duree desc</option>
<option value="duree-asc">Duree asc</option>
</select>
</div>
<div class="col-auto ms-auto">
<button id="fReset" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-x-circle me-1"></i>Reinitialiser
</button>
</div>
</div>
</div>
</div>
<!-- Resume -->
<div class="row g-2 mb-3">
<div class="col-auto">
<div class="card px-3 py-2">
<div class="text-muted" style="font-size:.68rem;text-transform:uppercase;letter-spacing:.05em">Commandes</div>
<div class="fw-bold fs-5" id="sumCount">{{ jobs|length }}</div>
</div>
</div>
<div class="col-auto">
<div class="card px-3 py-2">
<div class="text-muted" style="font-size:.68rem;text-transform:uppercase;letter-spacing:.05em">Total HT commandes</div>
<div class="fw-bold fs-5" id="sumHT">--</div>
</div>
</div>
<div class="col-auto">
<div class="card px-3 py-2">
<div class="text-muted" style="font-size:.68rem;text-transform:uppercase;letter-spacing:.05em">Total prix final</div>
<div class="fw-bold fs-5" id="sumFinal" style="color:#ff6b35">--</div>
</div>
</div>
<div class="col-auto">
<div class="card px-3 py-2">
<div class="text-muted" style="font-size:.68rem;text-transform:uppercase;letter-spacing:.05em">Poids total</div>
<div class="fw-bold fs-5" id="sumPoids">--</div>
</div>
</div>
</div>
<!-- Tableau -->
<div class="card">
<div class="card-body p-0">
{% if jobs %}
<div class="table-responsive">
<table class="table table-hover mb-0" id="jobsTable">
<thead class="table-light">
<tr>
<th style="width:90px">Date</th>
<th>Nom</th>
<th>Client</th>
<th>Matiere</th>
<th>Poids</th>
<th>Duree</th>
<th class="text-end">Prix HT</th>
<th class="text-end">Prix TTC</th>
<th></th>
</tr>
</thead>
<tbody id="jobsBody">
{% for j in jobs %}
{% set pieces = j.pieces_per_plate or 1 %}
{% set qty = j.order_qty or 1 %}
{% set prix_ht = j.prix_ht_piece or 0 %}
{% set poids_piece = j.weight_g / pieces %}
<tr
data-date="{{ j.created_at[:10] }}"
data-client="{{ j.client_id or '' }}"
data-prix="{{ '%.4f' | format(prix_ht * qty) }}"
data-poids="{{ j.weight_g }}"
data-duree="{{ j.print_time_s }}"
data-final="{{ '%.2f' | format((j.final_price or 0) * qty) }}"
>
<td class="text-muted small align-middle">{{ j.created_at[:10] }}</td>
<td class="align-middle">
<a href="{{ url_for('job_detail', id=j.id) }}" class="text-decoration-none fw-semibold">{{ j.name }}</a>
{% if j.plate_name %}<br><span class="text-muted" style="font-size:.72rem">{{ j.plate_name }}</span>{% endif %}
</td>
<td class="align-middle small">{{ j.client_name or '--' }}</td>
<td class="align-middle small">
{% if j.material_name %}<span class="badge bg-light text-dark border">{{ j.material_name }}</span>{% else %}<span class="text-muted">--</span>{% endif %}
</td>
<td class="align-middle">
<span class="fw-semibold">{{ j.weight_g }} g</span>
{% if pieces > 1 %}
<br><span class="text-muted" style="font-size:.72rem">{{ '%.1f'|format(poids_piece) }} g/pce x{{ pieces }}</span>
{% else %}
<br><span class="text-muted" style="font-size:.72rem">{{ '%.1f'|format(poids_piece) }} g/pce</span>
{% endif %}
</td>
<td class="align-middle small">{{ j.print_time_s | fmt_time }}</td>
{% set prix_ttc = (j.final_price / (1 - j.discount_pct / 100)) if (j.discount_pct and j.discount_pct > 0 and j.discount_pct < 100) else (j.final_price or 0) %}
<td class="align-middle text-end">
{% if prix_ht > 0 %}
<span class="fw-semibold">{{ '%.2f'|format(prix_ht) }} &euro;</span><span class="text-muted" style="font-size:.72rem"> HT/pce</span>
{% if qty > 1 %}
<br><span class="text-muted" style="font-size:.78rem">{{ '%.2f'|format(prix_ht * qty) }} &euro; x{{ qty }}</span>
{% endif %}
{% else %}--{% endif %}
</td>
<td class="align-middle text-end">
<span class="fw-semibold">{{ '%.2f'|format(prix_ttc) }} &euro;</span><span class="text-muted" style="font-size:.72rem"> TTC</span>
{% if j.discount_pct and j.discount_pct > 0 %}
<br><span class="fw-bold" style="color:#ff6b35">{{ '%.2f'|format(j.final_price or 0) }} &euro;</span>
<span class="badge bg-danger" style="font-size:.62rem">-{{ j.discount_pct }}%</span>
{% endif %}
{% if qty > 1 %}
<br><span class="text-muted" style="font-size:.72rem">
{{ '%.2f'|format(prix_ttc * qty) }} &euro; TTC tot.
{% if j.discount_pct and j.discount_pct > 0 %}
&rarr; <span style="color:#ff6b35">{{ '%.2f'|format((j.final_price or 0) * qty) }} &euro;</span>
{% endif %}
</span>
{% endif %}
</td>
<td class="align-middle">
<div class="d-flex gap-1 align-items-center">
{% if j.client_token %}
<a href="/portal/{{ j.client_token }}" target="_blank"
class="btn btn-sm btn-outline-success py-0" title="Portail client">
<i class="bi bi-share-fill"></i>
</a>
{% endif %}
<a href="{{ url_for('job_detail', id=j.id) }}" class="btn btn-sm btn-outline-secondary py-0">&rarr;</a>
<form method="POST" action="{{ url_for('delete_job', id=j.id) }}"
onsubmit="return confirm('Supprimer ?')">
<button class="btn btn-sm btn-outline-danger py-0"><i class="bi bi-trash"></i></button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div id="noResults" class="text-center text-muted py-4 d-none">
<i class="bi bi-search fs-2"></i>
<p class="mt-2 mb-0">Aucune commande ne correspond aux filtres.</p>
</div>
{% else %}
<div class="text-center text-muted py-5">
<i class="bi bi-inbox fs-1"></i>
<p class="mt-2">Aucune commande enregistree.</p>
<a href="{{ url_for('new_job') }}" class="btn btn-sm" style="background:#ff6b35;color:#fff">Creer le premier calcul</a>
</div>
{% endif %}
</div>
</div>
<script>
(function() {
var body = document.getElementById('jobsBody');
if (!body) return;
var rows = Array.from(body.querySelectorAll('tr'));
var fFrom = document.getElementById('fDateFrom');
var fTo = document.getElementById('fDateTo');
var fCli = document.getElementById('fClient');
var fSort = document.getElementById('fSort');
var noRes = document.getElementById('noResults');
function fmt(v) {
return new Intl.NumberFormat('fr-FR', {minimumFractionDigits:2, maximumFractionDigits:2}).format(v) + ' €';
}
function fmtG(v) {
return new Intl.NumberFormat('fr-FR', {maximumFractionDigits:0}).format(v) + ' g';
}
function apply() {
var from = fFrom.value;
var to = fTo.value;
var client = fCli.value;
var parts = fSort.value.split('-');
var key = parts[0];
var dir = parts[1];
var visible = rows.filter(function(r) {
if (from && r.dataset.date < from) return false;
if (to && r.dataset.date > to) return false;
if (client && r.dataset.client !== client) return false;
return true;
});
visible.sort(function(a, b) {
var va, vb;
if (key === 'date') { va = a.dataset.date; vb = b.dataset.date; }
if (key === 'prix') { va = parseFloat(a.dataset.prix); vb = parseFloat(b.dataset.prix); }
if (key === 'poids') { va = parseFloat(a.dataset.poids); vb = parseFloat(b.dataset.poids); }
if (key === 'duree') { va = parseFloat(a.dataset.duree); vb = parseFloat(b.dataset.duree); }
if (dir === 'asc') return va > vb ? 1 : va < vb ? -1 : 0;
return va < vb ? 1 : va > vb ? -1 : 0;
});
rows.forEach(function(r) { r.style.display = 'none'; });
visible.forEach(function(r) { r.style.display = ''; body.appendChild(r); });
noRes.classList.toggle('d-none', visible.length > 0);
var totalHT = 0, totalFinal = 0, totalPoids = 0;
visible.forEach(function(r) {
totalHT += parseFloat(r.dataset.prix) || 0;
totalFinal += parseFloat(r.dataset.final) || 0;
totalPoids += parseFloat(r.dataset.poids) || 0;
});
document.getElementById('sumCount').textContent = visible.length;
document.getElementById('sumHT').textContent = fmt(totalHT);
document.getElementById('sumFinal').textContent = fmt(totalFinal);
document.getElementById('sumPoids').textContent = fmtG(totalPoids);
}
fFrom.addEventListener('change', apply);
fTo.addEventListener('change', apply);
fCli.addEventListener('change', apply);
fSort.addEventListener('change', apply);
document.getElementById('fReset').addEventListener('click', function() {
fFrom.value = ''; fTo.value = ''; fCli.value = ''; fSort.value = 'date-desc';
apply();
});
apply();
})();
</script>
{% endblock %}