diff --git a/frontend/modules/transport/pages/carriers/[id]/index.vue b/frontend/modules/transport/pages/carriers/[id]/index.vue
index 63d6af8..c9d8311 100644
--- a/frontend/modules/transport/pages/carriers/[id]/index.vue
+++ b/frontend/modules/transport/pages/carriers/[id]/index.vue
@@ -173,28 +173,21 @@
-
+
-
- |
- {{ group.label }}
- |
- {{ row.party }} |
- {{ row.delivery }} |
- {{ row.apro }} |
- {{ row.forfait }} |
- {{ row.tonne }} |
- {{ row.indexation }} |
- {{ row.state }} |
+
+ {{ row.transport }} |
+ {{ row.party }} |
+ {{ row.delivery }} |
+ {{ row.apro }} |
+ {{ row.forfait }} |
+ {{ row.tonne }} |
+ {{ row.indexation }} |
+ {{ row.state }} |
@@ -343,10 +336,15 @@ function countryOptionsFor(country: string): SelectOption[] {
return country ? [{ value: country, label: country }] : []
}
-// ── Tableau Prix consultation (regroupé par contenant Fond Mouvant / Benne) ───
-const PRICE_GROUP_ORDER = ['FOND_MOUVANT', 'BENNE'] as const
+// ── Tableau Prix consultation (regroupé par ADRESSE DE LIVRAISON) ─────────────
+// Rang d'affichage des contenants au sein d'une même adresse (Fond mouvant puis Benne).
+const CONTAINER_RANK: Record = { FOND_MOUVANT: 0, BENNE: 1 }
interface PriceRowView {
+ /** Contenant (libellé affiché : Fond mouvant / Benne). */
+ transport: string
+ /** Contenant brut (FOND_MOUVANT / BENNE) — tri interne du groupe. */
+ transportType: string
/** Fournisseur ou client lié au prix (raison sociale). */
party: string
apro: string
@@ -357,9 +355,8 @@ interface PriceRowView {
state: string
}
-/** Groupe de prix d'un même contenant (Fond Mouvant / Benne) — cellule fusionnée. */
+/** Groupe de prix d'une même adresse de livraison (lignes consécutives). */
interface PriceGroupView {
- label: string
rows: PriceRowView[]
}
@@ -386,6 +383,7 @@ function siteCode(relation: Relation): string {
/**
* Construit une ligne d'affichage depuis un prix embarqué (maquette Prix) :
+ * - « Transport » = le contenant (Fond mouvant / Benne) ;
* - « Fournisseurs / Clients » = le fournisseur OU le client lié (raison sociale) ;
* - « Adresse sites » = le CODE du site (département, ex: 86 / 17 / 82) ;
* - « Adresse livraisons » = l'adresse (voie) du client/fournisseur ;
@@ -393,7 +391,10 @@ function siteCode(relation: Relation): string {
*/
function toPriceRow(price: CarrierPriceRead): PriceRowView {
const isClient = price.direction === 'CLIENT'
+ const containerType = price.containerType ?? ''
return {
+ transportType: containerType,
+ transport: containerType ? t(`transport.carriers.containerType.${containerType}`) : '',
party: labelOfRelation(isClient ? price.client : price.supplier),
apro: isClient ? siteCode(price.departureSite) : siteCode(price.deliverySite),
delivery: isClient ? labelOfRelation(price.clientDeliveryAddress) : labelOfRelation(price.supplierSupplyAddress),
@@ -413,28 +414,40 @@ function stateSuffix(state: string): string {
return map[state] ?? ''
}
-// Prix regroupés par contenant (Fond Mouvant puis Benne) — une cellule fusionnée
-// par groupe (rowspan) à gauche, conformément à la maquette.
+// Prix regroupés par ADRESSE DE LIVRAISON : les lignes d'une même adresse sont
+// consécutives (triées par contenant Fond mouvant → Benne), les groupes triés
+// alphabétiquement par adresse. Un séparateur épais sépare deux adresses.
const priceGroups = computed(() => {
- const list = carrier.value?.prices ?? []
- return PRICE_GROUP_ORDER
- .map(container => ({
- label: t(`transport.carriers.containerType.${container}`),
- rows: list.filter(p => p.containerType === container).map(toPriceRow),
+ const rows = (carrier.value?.prices ?? []).map(toPriceRow)
+ const byDelivery = new Map()
+ for (const row of rows) {
+ const list = byDelivery.get(row.delivery)
+ if (list) {
+ list.push(row)
+ } else {
+ byDelivery.set(row.delivery, [row])
+ }
+ }
+ return [...byDelivery.entries()]
+ .sort(([a], [b]) => a.localeCompare(b, 'fr'))
+ .map(([, groupRows]) => ({
+ rows: groupRows
+ .slice()
+ .sort((x, y) => (CONTAINER_RANK[x.transportType] ?? 99) - (CONTAINER_RANK[y.transportType] ?? 99)),
}))
- .filter(group => group.rows.length > 0)
})
const hasPrices = computed(() => priceGroups.value.length > 0)
/**
* Bordure basse d'une cellule de données :
- * - ligne interne d'un groupe → fine grise ;
- * - dernière ligne d'un groupe NON final → épaisse noire (séparateur de groupe) ;
+ * - ligne interne d'un groupe d'adresse → fine grise ;
+ * - dernière ligne d'un groupe NON final → épaisse noire (sépare deux adresses) ;
* - dernière ligne du DERNIER groupe → aucune (le cadre du tableau s'en charge,
* évite la double bordure tout en bas).
*/
-function dataBorder(group: PriceGroupView, i: number, gi: number): string {
+function dataBorder(gi: number, i: number): string {
+ const group = priceGroups.value[gi]
const isLastRow = i === group.rows.length - 1
const isLastGroup = gi === priceGroups.value.length - 1
if (!isLastRow) {
@@ -443,11 +456,6 @@ function dataBorder(group: PriceGroupView, i: number, gi: number): string {
return isLastGroup ? '' : 'border-b-2 border-black'
}
-/** Bordure basse de la cellule de groupe fusionnée (séparateur épais sauf dernier groupe). */
-function groupBorder(gi: number): string {
- return gi === priceGroups.value.length - 1 ? '' : 'border-b-2 border-black'
-}
-
// ── Export XLSX des prix ─────────────────────────────────────────────────────
const exporting = ref(false)