fix(transport) : tableau prix — colonne Fournisseurs/Clients, fusion adresses sites/livraisons, renomme Transport (ERP-193)
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m55s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m19s

This commit is contained in:
2026-06-19 10:50:25 +02:00
parent 0ad4a739ca
commit 865e580b6e
4 changed files with 38 additions and 23 deletions
+3 -4
View File
@@ -554,10 +554,9 @@
"message": "Ce transporteur réapparaîtra dans le répertoire actif. Confirmer la restauration ?" "message": "Ce transporteur réapparaîtra dans le répertoire actif. Confirmer la restauration ?"
}, },
"price": { "price": {
"group": "Type de transport", "group": "Transport",
"carrier": "Transporteurs", "carrier": "Fournisseurs / Clients",
"aproOrSite": "Adresse sites", "aproOrSite": "Adresse sites / livraisons",
"delivery": "Adresse livraisons",
"forfait": "Forfait (€)", "forfait": "Forfait (€)",
"tonne": "Tonne (€)", "tonne": "Tonne (€)",
"indexation": "Indexation", "indexation": "Indexation",
@@ -145,18 +145,18 @@
groupe (Fond Mouvant / Benne) fusionné en rowspan ; séparateur groupe (Fond Mouvant / Benne) fusionné en rowspan ; séparateur
épais entre les deux groupes. --> épais entre les deux groupes. -->
<table class="w-full table-fixed border-separate border-spacing-0 overflow-hidden rounded-malio border border-black text-left text-black"> <table class="w-full table-fixed border-separate border-spacing-0 overflow-hidden rounded-malio border border-black text-left text-black">
<!-- Répartition (table-fixed) : « Type de transport » un peu plus <!-- Répartition (table-fixed) : « Transport » étroit (libellé
large ; Transporteurs et Adresse livraisons larges ; Forfait / court Benne / Fond mouvant) ; Fournisseurs/Clients et
Tonne / Indexation / État réduits. --> Adresse sites/livraisons larges ; Forfait / Tonne /
Indexation / État réduits. -->
<colgroup> <colgroup>
<col class="w-[170px]" /> <col class="w-[120px]" />
<col class="w-[20%]" /> <col class="w-[18%]" />
<col class="w-[30%]" />
<col class="w-[10%]" />
<col class="w-[10%]" />
<col class="w-[11%]" />
<col class="w-[11%]" /> <col class="w-[11%]" />
<col class="w-[24%]" />
<col class="w-[9%]" />
<col class="w-[9%]" />
<col class="w-[9%]" />
<col class="w-[9%]" />
</colgroup> </colgroup>
<thead> <thead>
<tr> <tr>
@@ -164,7 +164,6 @@
<th class="border-b border-r border-black bg-m-surface px-3 py-3 text-center align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.group') }}</th> <th class="border-b border-r border-black bg-m-surface px-3 py-3 text-center align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.group') }}</th>
<th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.carrier') }}</th> <th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.carrier') }}</th>
<th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.aproOrSite') }}</th> <th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.aproOrSite') }}</th>
<th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.delivery') }}</th>
<th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.forfait') }}</th> <th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.forfait') }}</th>
<th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.tonne') }}</th> <th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.tonne') }}</th>
<th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.indexation') }}</th> <th class="border-b border-black bg-m-surface px-3 py-3 align-middle text-[16px] font-semibold">{{ t('transport.carriers.consultation.price.indexation') }}</th>
@@ -187,9 +186,13 @@
> >
{{ group.label }} {{ group.label }}
</td> </td>
<td class="px-3 py-4 text-[14px]" :class="dataBorder(group, i, gi)">{{ headerTitle }}</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.apro }}</td> <!-- Adresse sites / livraisons : code du site (département) puis
<td class="px-3 py-4 text-[14px]" :class="dataBorder(group, i, gi)">{{ row.delivery }}</td> adresse de livraison/appro, regroupés dans une seule cellule. -->
<td class="px-3 py-4 text-[14px]" :class="dataBorder(group, i, gi)">
<div v-if="row.apro" class="text-[12px] text-m-muted">{{ row.apro }}</div>
<div>{{ row.delivery }}</div>
</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.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.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.indexation }}</td>
@@ -197,7 +200,7 @@
</tr> </tr>
</template> </template>
<tr v-if="!hasPrices"> <tr v-if="!hasPrices">
<td colspan="8" class="px-3 py-4 text-center text-[14px] text-m-muted"> <td colspan="7" class="px-3 py-4 text-center text-[14px] text-m-muted">
{{ t('transport.carriers.consultation.price.empty') }} {{ t('transport.carriers.consultation.price.empty') }}
</td> </td>
</tr> </tr>
@@ -346,6 +349,8 @@ function countryOptionsFor(country: string): SelectOption[] {
const PRICE_GROUP_ORDER = ['FOND_MOUVANT', 'BENNE'] as const const PRICE_GROUP_ORDER = ['FOND_MOUVANT', 'BENNE'] as const
interface PriceRowView { interface PriceRowView {
/** Fournisseur ou client lié au prix (raison sociale). */
party: string
apro: string apro: string
delivery: string delivery: string
forfait: string forfait: string
@@ -383,13 +388,15 @@ 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) :
* - « Adresse sites » = le CODE du site (département, ex: 86 / 17 / 82) ; * - « Fournisseurs / Clients » = le fournisseur OU le client lié (raison sociale) ;
* - « Adresse livraisons » = l'adresse (voie) du client/fournisseur ; * - « Adresse sites / livraisons » = code du site (département) + adresse de
* livraison/appro du client/fournisseur, regroupés dans une seule colonne ;
* - le prix tombe dans Forfait € OU Tonne € selon `pricingUnit`. * - le prix tombe dans Forfait € OU Tonne € selon `pricingUnit`.
*/ */
function toPriceRow(price: CarrierPriceRead): PriceRowView { function toPriceRow(price: CarrierPriceRead): PriceRowView {
const isClient = price.direction === 'CLIENT' const isClient = price.direction === 'CLIENT'
return { return {
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),
forfait: price.pricingUnit === 'FORFAIT' ? formatAmount(price.price) : '', forfait: price.pricingUnit === 'FORFAIT' ? formatAmount(price.price) : '',
@@ -27,6 +27,10 @@ describe('carrierMappers', () => {
expect(iriOf(undefined)).toBeNull() expect(iriOf(undefined)).toBeNull()
}) })
it('labelOfRelation : companyName (client/fournisseur) prioritaire sur name/adresse', () => {
expect(labelOfRelation({ '@id': '/api/suppliers/8', companyName: 'AAAAAAA', name: 'X' })).toBe('AAAAAAA')
})
it('labelOfRelation : name (site) à défaut adresse condensée', () => { it('labelOfRelation : name (site) à défaut adresse condensée', () => {
expect(labelOfRelation({ '@id': '/api/sites/1', name: 'Châtellerault' })).toBe('Châtellerault') expect(labelOfRelation({ '@id': '/api/sites/1', name: 'Châtellerault' })).toBe('Châtellerault')
expect(labelOfRelation({ '@id': '/api/client_addresses/8', street: '1 rue X', postalCode: '86000', city: 'Poitiers' })).toBe('1 rue X · 86000 · Poitiers') expect(labelOfRelation({ '@id': '/api/client_addresses/8', street: '1 rue X', postalCode: '86000', city: 'Poitiers' })).toBe('1 rue X · 86000 · Poitiers')
@@ -97,13 +97,18 @@ export function iriOf(relation: Relation): string | null {
} }
/** /**
* Libellé d'affichage d'une relation embarquée : `name` (site) à défaut une adresse * Libellé d'affichage d'une relation embarquée : `companyName` (client/fournisseur)
* condensée (voie · CP · ville). Chaîne vide si la relation est un IRI nu / absente. * à défaut `name` (site), à défaut une adresse condensée (voie · CP · ville). Chaîne
* vide si la relation est un IRI nu / absente.
*/ */
export function labelOfRelation(relation: Relation): string { export function labelOfRelation(relation: Relation): string {
if (!relation || typeof relation === 'string') { if (!relation || typeof relation === 'string') {
return '' return ''
} }
const companyName = relation.companyName as string | undefined
if (companyName) {
return companyName
}
const name = relation.name as string | undefined const name = relation.name as string | undefined
if (name) { if (name) {
return name return name