fix(transport) : tableau prix consultation — cellule de groupe fusionnée (Fond Mouvant/Benne) + colonnes maquette (ERP-170)
This commit is contained in:
@@ -102,43 +102,52 @@
|
||||
<!-- Prix : tableau présentationnel regroupé par contenant + export. -->
|
||||
<template #prices>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<table class="w-full border-collapse text-left text-sm">
|
||||
<table class="w-full border-collapse border border-m-muted/40 text-left text-sm">
|
||||
<thead>
|
||||
<tr class="border-b border-black">
|
||||
<th class="py-2 font-semibold">{{ t('transport.carriers.consultation.price.group') }}</th>
|
||||
<th class="py-2 font-semibold">{{ t('transport.carriers.consultation.price.carrier') }}</th>
|
||||
<th class="py-2 font-semibold">{{ t('transport.carriers.consultation.price.aproOrSite') }}</th>
|
||||
<th class="py-2 font-semibold">{{ t('transport.carriers.consultation.price.delivery') }}</th>
|
||||
<th class="py-2 font-semibold">{{ t('transport.carriers.consultation.price.forfait') }}</th>
|
||||
<th class="py-2 font-semibold">{{ t('transport.carriers.consultation.price.tonne') }}</th>
|
||||
<th class="py-2 font-semibold">{{ t('transport.carriers.consultation.price.indexation') }}</th>
|
||||
<th class="py-2 font-semibold">{{ t('transport.carriers.consultation.price.state') }}</th>
|
||||
<tr class="border-b border-black bg-m-muted/10">
|
||||
<th class="px-3 py-2"></th>
|
||||
<th class="px-3 py-2 font-semibold">{{ t('transport.carriers.consultation.price.carrier') }}</th>
|
||||
<th class="px-3 py-2 font-semibold">{{ t('transport.carriers.consultation.price.aproOrSite') }}</th>
|
||||
<th class="px-3 py-2 font-semibold">{{ t('transport.carriers.consultation.price.delivery') }}</th>
|
||||
<th class="px-3 py-2 font-semibold">{{ t('transport.carriers.consultation.price.forfait') }}</th>
|
||||
<th class="px-3 py-2 font-semibold">{{ t('transport.carriers.consultation.price.tonne') }}</th>
|
||||
<th class="px-3 py-2 font-semibold">{{ t('transport.carriers.consultation.price.indexation') }}</th>
|
||||
<th class="px-3 py-2 font-semibold">{{ t('transport.carriers.consultation.price.state') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(row, index) in priceRows"
|
||||
:key="index"
|
||||
class="border-b border-m-muted/30"
|
||||
>
|
||||
<td class="py-2 font-medium">{{ row.group }}</td>
|
||||
<td class="py-2">{{ headerTitle }}</td>
|
||||
<td class="py-2">{{ row.aproOrSite }}</td>
|
||||
<td class="py-2">{{ row.delivery }}</td>
|
||||
<td class="py-2">{{ row.forfait }}</td>
|
||||
<td class="py-2">{{ row.tonne }}</td>
|
||||
<td class="py-2">{{ row.indexation }}</td>
|
||||
<td class="py-2">{{ row.state }}</td>
|
||||
</tr>
|
||||
<tr v-if="priceRows.length === 0">
|
||||
<td colspan="8" class="py-4 text-center text-m-muted">
|
||||
<template v-for="(group, gi) in priceGroups" :key="group.label">
|
||||
<tr
|
||||
v-for="(row, i) in group.rows"
|
||||
:key="`${gi}-${i}`"
|
||||
:class="i === group.rows.length - 1 ? 'border-b border-black' : 'border-b border-m-muted/30'"
|
||||
>
|
||||
<!-- Cellule de groupe fusionnée (rowspan), centrée verticalement. -->
|
||||
<td
|
||||
v-if="i === 0"
|
||||
:rowspan="group.rows.length"
|
||||
class="border-r border-black px-3 py-2 text-center align-middle font-medium"
|
||||
>
|
||||
{{ group.label }}
|
||||
</td>
|
||||
<td class="px-3 py-2">{{ headerTitle }}</td>
|
||||
<td class="px-3 py-2">{{ row.apro }}</td>
|
||||
<td class="px-3 py-2">{{ row.delivery }}</td>
|
||||
<td class="px-3 py-2">{{ row.forfait }}</td>
|
||||
<td class="px-3 py-2">{{ row.tonne }}</td>
|
||||
<td class="px-3 py-2">{{ row.indexation }}</td>
|
||||
<td class="px-3 py-2">{{ row.state }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
<tr v-if="!hasPrices">
|
||||
<td colspan="8" class="px-3 py-4 text-center text-m-muted">
|
||||
{{ t('transport.carriers.consultation.price.empty') }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div v-if="priceRows.length > 0" class="flex justify-center">
|
||||
<div v-if="hasPrices" class="flex justify-center">
|
||||
<MalioButton
|
||||
variant="primary"
|
||||
:label="t('transport.carriers.consultation.price.export')"
|
||||
@@ -256,8 +265,7 @@ function countryOptionsFor(country: string): SelectOption[] {
|
||||
const PRICE_GROUP_ORDER = ['FOND_MOUVANT', 'BENNE'] as const
|
||||
|
||||
interface PriceRowView {
|
||||
group: string
|
||||
aproOrSite: string
|
||||
apro: string
|
||||
delivery: string
|
||||
forfait: string
|
||||
tonne: string
|
||||
@@ -265,17 +273,41 @@ interface PriceRowView {
|
||||
state: string
|
||||
}
|
||||
|
||||
/** Construit une ligne d'affichage depuis un prix embarqué. */
|
||||
/** Groupe de prix d'un même contenant (Fond Mouvant / Benne) — cellule fusionnée. */
|
||||
interface PriceGroupView {
|
||||
label: string
|
||||
rows: PriceRowView[]
|
||||
}
|
||||
|
||||
/** Formate un montant décimal en « 1 000,00 € » (chaîne vide si absent). */
|
||||
function formatAmount(value: string | null | undefined): string {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
const n = Number(value)
|
||||
if (Number.isNaN(n)) {
|
||||
return ''
|
||||
}
|
||||
return `${n.toLocaleString('fr-FR', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} €`
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit une ligne d'affichage depuis un prix embarqué (maquette Prix) :
|
||||
* - « Adresse APRO » = adresse (voie) du client/fournisseur ;
|
||||
* - « Adresse livraisons » = le site (86 / 17 / 82) ;
|
||||
* - le prix tombe dans Forfait € OU Tonne € selon `pricingUnit`.
|
||||
*/
|
||||
function toPriceRow(price: CarrierPriceRead): PriceRowView {
|
||||
const isClient = price.direction === 'CLIENT'
|
||||
return {
|
||||
group: price.containerType ? t(`transport.carriers.containerType.${price.containerType}`) : '',
|
||||
// RG : prix Client → site de départ ; prix Fournisseur → adresse d'appro.
|
||||
aproOrSite: isClient ? labelOfRelation(price.departureSite) : labelOfRelation(price.supplierSupplyAddress),
|
||||
delivery: isClient ? labelOfRelation(price.clientDeliveryAddress) : labelOfRelation(price.deliverySite),
|
||||
forfait: price.pricingUnit === 'FORFAIT' ? (price.price ?? '') : '',
|
||||
tonne: price.pricingUnit === 'TONNE' ? (price.price ?? '') : '',
|
||||
indexation: main.value.indexationRate || '',
|
||||
apro: isClient ? labelOfRelation(price.clientDeliveryAddress) : labelOfRelation(price.supplierSupplyAddress),
|
||||
delivery: isClient ? labelOfRelation(price.departureSite) : labelOfRelation(price.deliverySite),
|
||||
forfait: price.pricingUnit === 'FORFAIT' ? formatAmount(price.price) : '',
|
||||
tonne: price.pricingUnit === 'TONNE' ? formatAmount(price.price) : '',
|
||||
// CarrierPrice n'a pas de taux d'indexation propre → on affiche celui du
|
||||
// transporteur (formulaire principal). À faire évoluer si un taux par prix
|
||||
// est requis (gap back).
|
||||
indexation: main.value.indexationRate ? `${main.value.indexationRate} %` : '',
|
||||
state: price.priceState ? t(`transport.carriers.form.price.state${stateSuffix(price.priceState)}`) : '',
|
||||
}
|
||||
}
|
||||
@@ -286,15 +318,20 @@ function stateSuffix(state: string): string {
|
||||
return map[state] ?? ''
|
||||
}
|
||||
|
||||
// Prix triés/regroupés par contenant (Fond Mouvant puis Benne).
|
||||
const priceRows = computed<PriceRowView[]>(() => {
|
||||
// Prix regroupés par contenant (Fond Mouvant puis Benne) — une cellule fusionnée
|
||||
// par groupe (rowspan) à gauche, conformément à la maquette.
|
||||
const priceGroups = computed<PriceGroupView[]>(() => {
|
||||
const list = carrier.value?.prices ?? []
|
||||
return [...list]
|
||||
.sort((a, b) => PRICE_GROUP_ORDER.indexOf((a.containerType ?? '') as 'FOND_MOUVANT')
|
||||
- PRICE_GROUP_ORDER.indexOf((b.containerType ?? '') as 'FOND_MOUVANT'))
|
||||
.map(toPriceRow)
|
||||
return PRICE_GROUP_ORDER
|
||||
.map(container => ({
|
||||
label: t(`transport.carriers.containerType.${container}`),
|
||||
rows: list.filter(p => p.containerType === container).map(toPriceRow),
|
||||
}))
|
||||
.filter(group => group.rows.length > 0)
|
||||
})
|
||||
|
||||
const hasPrices = computed(() => priceGroups.value.length > 0)
|
||||
|
||||
// ── Export XLSX des prix ─────────────────────────────────────────────────────
|
||||
const exporting = ref(false)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user