f8f7571cc0
- RouteEngineInterface (computeMatrix/optimizeOrder/estimateLegDurations) + HaversineRouteEngine V1 (vitesse moyenne parametrable, plus proche voisin)
- TourRouteCalculator : resolution coords, ETA (RG-6.11), exclusion sans coords (RG-6.05), totaux ; optimize = reorder + recompute
- Endpoints API Platform POST /tours/{id}/compute, /optimize, /duplicate (TourDuplicator, RG-6.13) + Processors, security manage
- Feuille de route PDF GET /tours/{id}/roadbook.pdf (Dompdf + Twig) via PdfRendererInterface (Shared), controller priority:1, security view
- TierAddressResolver etendu (coords + location DBAL)
- Tests : HaversineRouteEngine (unit), compute/optimize/duplicate/roadbook (API)
89 lines
3.6 KiB
Twig
89 lines
3.6 KiB
Twig
{# Feuille de route PDF d'une tournee (M6.4). Template autonome (Dompdf) : styles
|
|
inline / <style>, pas d'heritage de base.html.twig ni de ressource distante. #}
|
|
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<style>
|
|
@page { margin: 24px 28px; }
|
|
* { font-family: "DejaVu Sans", sans-serif; }
|
|
body { color: #1f2937; font-size: 11px; margin: 0; }
|
|
|
|
.header { border-bottom: 2px solid #111827; padding-bottom: 10px; margin-bottom: 14px; }
|
|
.header h1 { font-size: 18px; margin: 0 0 6px; color: #111827; }
|
|
.meta { width: 100%; }
|
|
.meta td { font-size: 11px; padding: 1px 0; vertical-align: top; }
|
|
.meta .label { color: #6b7280; width: 90px; }
|
|
|
|
.totals { margin: 0 0 14px; }
|
|
.totals td { background: #f3f4f6; border: 1px solid #e5e7eb; padding: 6px 10px; font-size: 11px; }
|
|
.totals .value { font-size: 14px; font-weight: bold; color: #111827; }
|
|
|
|
table.stops { width: 100%; border-collapse: collapse; }
|
|
table.stops th { background: #111827; color: #fff; font-size: 10px; text-align: left; padding: 6px 7px; }
|
|
table.stops td { border-bottom: 1px solid #e5e7eb; padding: 6px 7px; font-size: 10px; vertical-align: top; }
|
|
table.stops tr:nth-child(even) td { background: #f9fafb; }
|
|
.num { text-align: center; font-weight: bold; width: 22px; }
|
|
.nowrap { white-space: nowrap; }
|
|
.muted { color: #6b7280; }
|
|
.notes { width: 130px; }
|
|
.notes-box { border: 1px dashed #9ca3af; height: 26px; }
|
|
|
|
.footer { margin-top: 16px; font-size: 9px; color: #9ca3af; text-align: center; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>Feuille de route — {{ tour.label }}</h1>
|
|
<table class="meta">
|
|
<tr>
|
|
<td class="label">Date</td><td>{{ tour.date }}</td>
|
|
<td class="label">Commercial</td><td>{{ tour.commercial }}</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label">Départ</td><td>{{ tour.departureTime }}{% if tour.startLabel %} — {{ tour.startLabel }}{% endif %}</td>
|
|
<td class="label">Étapes</td><td>{{ tour.stopCount }}</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
|
|
<table class="totals">
|
|
<tr>
|
|
<td>Distance totale<br><span class="value">{{ tour.totalDistance }}</span></td>
|
|
<td>Durée totale<br><span class="value">{{ tour.totalDuration }}</span></td>
|
|
</tr>
|
|
</table>
|
|
|
|
<table class="stops">
|
|
<thead>
|
|
<tr>
|
|
<th class="num">#</th>
|
|
<th class="nowrap">ETA</th>
|
|
<th class="nowrap">Visite</th>
|
|
<th>Tiers / Point</th>
|
|
<th>Adresse</th>
|
|
<th class="nowrap">Trajet précédent</th>
|
|
<th class="notes">Notes</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for stop in stops %}
|
|
<tr>
|
|
<td class="num">{{ stop.number }}</td>
|
|
<td class="nowrap">{{ stop.eta }}</td>
|
|
<td class="nowrap">{{ stop.visitMinutes }} min</td>
|
|
<td>{{ stop.name }}</td>
|
|
<td>{{ stop.address|default('—') }}</td>
|
|
<td class="nowrap muted">{{ stop.legDuration }} · {{ stop.legDistance }}</td>
|
|
<td class="notes"><div class="notes-box"></div></td>
|
|
</tr>
|
|
{% else %}
|
|
<tr><td colspan="7" class="muted">Aucune étape dans cette tournée.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="footer">Feuille de route générée le {{ "now"|date("d/m/Y") }} — Starseed</div>
|
|
</body>
|
|
</html>
|