Files
Starseed/templates/field_sales/roadbook.html.twig
T
Matthieu f8f7571cc0
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Failing after 52s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Failing after 11s
feat(field_sales) : calcul de trajet, optimisation, duplication & roadbook PDF (ERP-125)
- 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)
2026-06-11 16:46:49 +02:00

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>