feat(transport) : contenant du formulaire principal en radios centrés (Benne par défaut) (ERP-170)
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m58s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m25s

This commit is contained in:
2026-06-17 14:34:46 +02:00
parent 9c3aebf02e
commit d446ba9bf4
4 changed files with 55 additions and 35 deletions
@@ -111,6 +111,8 @@ describe('useCarrierForm', () => {
form.main.name = 'Acme' form.main.name = 'Acme'
form.main.certificationType = 'GMP_PLUS' form.main.certificationType = 'GMP_PLUS'
form.main.isChartered = true form.main.isChartered = true
// Annule le défaut « BENNE » pour vérifier la garde « contenant obligatoire ».
form.main.containerType = null
const created = await form.submitMain() const created = await form.submitMain()
@@ -323,16 +325,18 @@ describe('useCarrierForm — champs conditionnels (ERP-166)', () => {
}) })
}) })
it('RG-4.03 affrété mais champs vides : omis du payload (422 NotBlank back)', () => { it('RG-4.03 affrété, indexation/volume vides : omis du payload (containerType garde son défaut BENNE)', () => {
const form = useCarrierForm() const form = useCarrierForm()
form.main.name = 'Acme' form.main.name = 'Acme'
form.main.certificationType = 'GMP_PLUS' form.main.certificationType = 'GMP_PLUS'
form.main.isChartered = true form.main.isChartered = true
// indexation / volume vides → omis (422 NotBlank back) ; containerType défaut « BENNE » envoyé.
expect(form.buildMainPayload()).toEqual({ expect(form.buildMainPayload()).toEqual({
name: 'Acme', name: 'Acme',
certificationType: 'GMP_PLUS', certificationType: 'GMP_PLUS',
isChartered: true, isChartered: true,
containerType: 'BENNE',
}) })
}) })
@@ -73,15 +73,29 @@
:error="mainErrors.errors.indexationRate" :error="mainErrors.errors.indexationRate"
@update:model-value="onIndexationInput" @update:model-value="onIndexationInput"
/> />
<MalioSelect <!-- Contenant : Benne / Fond mouvant en radios, centrés (h-12) comme
:model-value="main.containerType" à l'onglet Prix (Benne par défaut). -->
:options="containerOptions" <div>
:label="t('transport.carriers.form.main.containerType')" <div class="flex h-12 items-center gap-4">
empty-option-label="" <MalioRadioButton
:required="true" :model-value="main.containerType"
:error="mainErrors.errors.containerType" name="carrier-main-container"
@update:model-value="(v: string | number | null) => main.containerType = v === null ? null : String(v)" value="BENNE"
/> :label="t('transport.carriers.containerType.BENNE')"
group-class="mt-0"
@update:model-value="(v: string | number | boolean | null) => main.containerType = v === null ? null : String(v)"
/>
<MalioRadioButton
:model-value="main.containerType"
name="carrier-main-container"
value="FOND_MOUVANT"
:label="t('transport.carriers.containerType.FOND_MOUVANT')"
group-class="mt-0"
@update:model-value="(v: string | number | boolean | null) => main.containerType = v === null ? null : String(v)"
/>
</div>
<p v-if="mainErrors.errors.containerType" class="ml-[2px] text-xs text-m-danger">{{ mainErrors.errors.containerType }}</p>
</div>
<MalioInputText <MalioInputText
:model-value="main.volumeM3" :model-value="main.volumeM3"
:label="t('transport.carriers.form.main.volumeM3')" :label="t('transport.carriers.form.main.volumeM3')"
@@ -250,10 +264,6 @@ const certificationOptions = computed<SelectOption[]>(() => {
return codes.map(code => ({ value: code, label: t(`transport.carriers.certification.${code}`) })) return codes.map(code => ({ value: code, label: t(`transport.carriers.certification.${code}`) }))
}) })
const CONTAINER_TYPES = ['BENNE', 'FOND_MOUVANT'] as const
const containerOptions = computed<SelectOption[]>(() =>
CONTAINER_TYPES.map(code => ({ value: code, label: t(`transport.carriers.containerType.${code}`) })),
)
const TAB_ICONS: Record<string, string> = { const TAB_ICONS: Record<string, string> = {
addresses: 'mdi:map-marker-outline', addresses: 'mdi:map-marker-outline',
@@ -101,17 +101,31 @@
@update:model-value="onIndexationInput" @update:model-value="onIndexationInput"
/> />
<!-- Contenant : Benne / Fond mouvant (RG-4.03). --> <!-- Contenant : Benne / Fond mouvant en radios, centrés (h-12) comme
<MalioSelect à l'onglet Prix (Benne par défaut). -->
:model-value="main.containerType" <div>
:options="containerOptions" <div class="flex h-12 items-center gap-4">
:label="t('transport.carriers.form.main.containerType')" <MalioRadioButton
empty-option-label="" :model-value="main.containerType"
:required="true" name="carrier-main-container"
:readonly="mainLocked" value="BENNE"
:error="mainErrors.errors.containerType" :label="t('transport.carriers.containerType.BENNE')"
@update:model-value="(v: string | number | null) => main.containerType = v === null ? null : String(v)" :disabled="mainLocked"
/> group-class="mt-0"
@update:model-value="(v: string | number | boolean | null) => main.containerType = v === null ? null : String(v)"
/>
<MalioRadioButton
:model-value="main.containerType"
name="carrier-main-container"
value="FOND_MOUVANT"
:label="t('transport.carriers.containerType.FOND_MOUVANT')"
:disabled="mainLocked"
group-class="mt-0"
@update:model-value="(v: string | number | boolean | null) => main.containerType = v === null ? null : String(v)"
/>
</div>
<p v-if="mainErrors.errors.containerType" class="ml-[2px] text-xs text-m-danger">{{ mainErrors.errors.containerType }}</p>
</div>
<!-- Volume m³ : champ texte restreint aux nombres à décimales (point). --> <!-- Volume m³ : champ texte restreint aux nombres à décimales (point). -->
<MalioInputText <MalioInputText
@@ -474,15 +488,6 @@ const qualimatEmptyMessage = computed(() => hasQualimatSearch.value
? t('transport.carriers.form.qualimat.empty') ? t('transport.carriers.form.qualimat.empty')
: t('transport.carriers.form.qualimat.searchHint')) : t('transport.carriers.form.qualimat.searchHint'))
// Contenant (RG-4.03) : Benne / Fond mouvant — select simple.
const CONTAINER_TYPES = ['BENNE', 'FOND_MOUVANT'] as const
const containerOptions = computed<SelectOption[]>(() =>
CONTAINER_TYPES.map(code => ({
value: code,
label: t(`transport.carriers.containerType.${code}`),
})),
)
// Icone (Iconify) affichee dans chaque onglet, par cle. // Icone (Iconify) affichee dans chaque onglet, par cle.
const TAB_ICONS: Record<string, string> = { const TAB_ICONS: Record<string, string> = {
@@ -40,7 +40,8 @@ export function emptyCarrierMain(): CarrierMainDraft {
certificationType: null, certificationType: null,
isChartered: false, isChartered: false,
indexationRate: '', indexationRate: '',
containerType: null, // Défaut métier : Benne pré-sélectionné (radio du formulaire principal).
containerType: 'BENNE',
volumeM3: '', volumeM3: '',
liotPlates: '', liotPlates: '',
dischargeDocumentIri: null, dischargeDocumentIri: null,