fix(transport) : tableau prix — regroupement et tri par adresse de livraison, contenant par ligne (ERP-193)
This commit is contained in:
@@ -173,28 +173,21 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<template v-for="(group, gi) in priceGroups" :key="group.label">
|
<template v-for="(group, gi) in priceGroups" :key="gi">
|
||||||
<tr
|
<tr
|
||||||
v-for="(row, i) in group.rows"
|
v-for="(row, i) in group.rows"
|
||||||
:key="`${gi}-${i}`"
|
:key="`${gi}-${i}`"
|
||||||
>
|
>
|
||||||
<!-- Cellule de groupe fusionnée (rowspan), centrée verticalement ;
|
<!-- Contenant par ligne (plus de cellule fusionnée) ; séparateur
|
||||||
séparateur épais en bas entre les groupes (sauf dernier). -->
|
à droite, comme l'ancienne colonne de groupe. -->
|
||||||
<td
|
<td class="border-r border-black px-3 py-4 text-center align-middle text-[14px] font-medium" :class="dataBorder(gi, i)">{{ row.transport }}</td>
|
||||||
v-if="i === 0"
|
<td class="px-3 py-4 text-[14px]" :class="dataBorder(gi, i)">{{ row.party }}</td>
|
||||||
:rowspan="group.rows.length"
|
<td class="px-3 py-4 text-[14px]" :class="dataBorder(gi, i)">{{ row.delivery }}</td>
|
||||||
class="border-r border-black px-3 text-center align-middle text-[14px] font-medium"
|
<td class="px-3 py-4 text-[14px]" :class="dataBorder(gi, i)">{{ row.apro }}</td>
|
||||||
:class="groupBorder(gi)"
|
<td class="px-3 py-4 text-[14px]" :class="dataBorder(gi, i)">{{ row.forfait }}</td>
|
||||||
>
|
<td class="px-3 py-4 text-[14px]" :class="dataBorder(gi, i)">{{ row.tonne }}</td>
|
||||||
{{ group.label }}
|
<td class="px-3 py-4 text-[14px]" :class="dataBorder(gi, i)">{{ row.indexation }}</td>
|
||||||
</td>
|
<td class="px-3 py-4 text-[14px]" :class="dataBorder(gi, i)">{{ row.state }}</td>
|
||||||
<td class="px-3 py-4 text-[14px]" :class="dataBorder(group, i, gi)">{{ row.party }}</td>
|
|
||||||
<td class="px-3 py-4 text-[14px]" :class="dataBorder(group, i, gi)">{{ row.delivery }}</td>
|
|
||||||
<td class="px-3 py-4 text-[14px]" :class="dataBorder(group, i, gi)">{{ row.apro }}</td>
|
|
||||||
<td class="px-3 py-4 text-[14px]" :class="dataBorder(group, i, gi)">{{ row.forfait }}</td>
|
|
||||||
<td class="px-3 py-4 text-[14px]" :class="dataBorder(group, i, gi)">{{ row.tonne }}</td>
|
|
||||||
<td class="px-3 py-4 text-[14px]" :class="dataBorder(group, i, gi)">{{ row.indexation }}</td>
|
|
||||||
<td class="px-3 py-4 text-[14px]" :class="dataBorder(group, i, gi)">{{ row.state }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<tr v-if="!hasPrices">
|
<tr v-if="!hasPrices">
|
||||||
@@ -343,10 +336,15 @@ function countryOptionsFor(country: string): SelectOption[] {
|
|||||||
return country ? [{ value: country, label: country }] : []
|
return country ? [{ value: country, label: country }] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Tableau Prix consultation (regroupé par contenant Fond Mouvant / Benne) ───
|
// ── Tableau Prix consultation (regroupé par ADRESSE DE LIVRAISON) ─────────────
|
||||||
const PRICE_GROUP_ORDER = ['FOND_MOUVANT', 'BENNE'] as const
|
// Rang d'affichage des contenants au sein d'une même adresse (Fond mouvant puis Benne).
|
||||||
|
const CONTAINER_RANK: Record<string, number> = { FOND_MOUVANT: 0, BENNE: 1 }
|
||||||
|
|
||||||
interface PriceRowView {
|
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). */
|
/** Fournisseur ou client lié au prix (raison sociale). */
|
||||||
party: string
|
party: string
|
||||||
apro: string
|
apro: string
|
||||||
@@ -357,9 +355,8 @@ interface PriceRowView {
|
|||||||
state: string
|
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 {
|
interface PriceGroupView {
|
||||||
label: string
|
|
||||||
rows: PriceRowView[]
|
rows: PriceRowView[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,6 +383,7 @@ function siteCode(relation: Relation): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Construit une ligne d'affichage depuis un prix embarqué (maquette Prix) :
|
* 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) ;
|
* - « Fournisseurs / Clients » = le fournisseur OU le client lié (raison sociale) ;
|
||||||
* - « Adresse sites » = le CODE du site (département, ex: 86 / 17 / 82) ;
|
* - « Adresse sites » = le CODE du site (département, ex: 86 / 17 / 82) ;
|
||||||
* - « Adresse livraisons » = l'adresse (voie) du client/fournisseur ;
|
* - « Adresse livraisons » = l'adresse (voie) du client/fournisseur ;
|
||||||
@@ -393,7 +391,10 @@ function siteCode(relation: Relation): string {
|
|||||||
*/
|
*/
|
||||||
function toPriceRow(price: CarrierPriceRead): PriceRowView {
|
function toPriceRow(price: CarrierPriceRead): PriceRowView {
|
||||||
const isClient = price.direction === 'CLIENT'
|
const isClient = price.direction === 'CLIENT'
|
||||||
|
const containerType = price.containerType ?? ''
|
||||||
return {
|
return {
|
||||||
|
transportType: containerType,
|
||||||
|
transport: containerType ? t(`transport.carriers.containerType.${containerType}`) : '',
|
||||||
party: labelOfRelation(isClient ? price.client : price.supplier),
|
party: labelOfRelation(isClient ? price.client : price.supplier),
|
||||||
apro: isClient ? siteCode(price.departureSite) : siteCode(price.deliverySite),
|
apro: isClient ? siteCode(price.departureSite) : siteCode(price.deliverySite),
|
||||||
delivery: isClient ? labelOfRelation(price.clientDeliveryAddress) : labelOfRelation(price.supplierSupplyAddress),
|
delivery: isClient ? labelOfRelation(price.clientDeliveryAddress) : labelOfRelation(price.supplierSupplyAddress),
|
||||||
@@ -413,28 +414,40 @@ function stateSuffix(state: string): string {
|
|||||||
return map[state] ?? ''
|
return map[state] ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prix regroupés par contenant (Fond Mouvant puis Benne) — une cellule fusionnée
|
// Prix regroupés par ADRESSE DE LIVRAISON : les lignes d'une même adresse sont
|
||||||
// par groupe (rowspan) à gauche, conformément à la maquette.
|
// 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<PriceGroupView[]>(() => {
|
const priceGroups = computed<PriceGroupView[]>(() => {
|
||||||
const list = carrier.value?.prices ?? []
|
const rows = (carrier.value?.prices ?? []).map(toPriceRow)
|
||||||
return PRICE_GROUP_ORDER
|
const byDelivery = new Map<string, PriceRowView[]>()
|
||||||
.map(container => ({
|
for (const row of rows) {
|
||||||
label: t(`transport.carriers.containerType.${container}`),
|
const list = byDelivery.get(row.delivery)
|
||||||
rows: list.filter(p => p.containerType === container).map(toPriceRow),
|
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)
|
const hasPrices = computed(() => priceGroups.value.length > 0)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bordure basse d'une cellule de données :
|
* Bordure basse d'une cellule de données :
|
||||||
* - ligne interne d'un groupe → fine grise ;
|
* - ligne interne d'un groupe d'adresse → fine grise ;
|
||||||
* - dernière ligne d'un groupe NON final → épaisse noire (séparateur de groupe) ;
|
* - 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,
|
* - dernière ligne du DERNIER groupe → aucune (le cadre du tableau s'en charge,
|
||||||
* évite la double bordure tout en bas).
|
* é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 isLastRow = i === group.rows.length - 1
|
||||||
const isLastGroup = gi === priceGroups.value.length - 1
|
const isLastGroup = gi === priceGroups.value.length - 1
|
||||||
if (!isLastRow) {
|
if (!isLastRow) {
|
||||||
@@ -443,11 +456,6 @@ function dataBorder(group: PriceGroupView, i: number, gi: number): string {
|
|||||||
return isLastGroup ? '' : 'border-b-2 border-black'
|
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 ─────────────────────────────────────────────────────
|
// ── Export XLSX des prix ─────────────────────────────────────────────────────
|
||||||
const exporting = ref(false)
|
const exporting = ref(false)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user