fix(front) : masque les champs non remplis en consultation sur les 4 modules (ERP-193)
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 48s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m18s

En consultation (lecture seule), les champs vides et les cases a cocher non
cochees ne sont plus affiches (client, fournisseur, prestataire, transporteur).
Util partage isFilled (vide = null/chaine vide/multiselect vide/false ; 0 reste
affiche). Nouveau prop hideEmpty sur les blocs contact/adresse, passe uniquement
en consultation (edition inchangee).
This commit is contained in:
2026-06-22 11:19:38 +02:00
parent bd71caa289
commit 6b714b81c4
14 changed files with 221 additions and 46 deletions
@@ -40,7 +40,9 @@
@update:model-value="(v: (string | number)[]) => update('siteIris', v.map(String))"
/>
<!-- Contacts rattaches (M2M, facultatif). Consultation : masque si aucun (ERP-193). -->
<MalioSelectCheckbox
v-if="!hideEmpty || isFilled(model.contactIris)"
:model-value="model.contactIris"
:options="contactOptions"
:label="t('commercial.clients.form.address.contacts')"
@@ -68,7 +70,10 @@
@update:model-value="(v: string) => update('billingEmail', v)"
@add="revealSecondaryBillingEmail"
/>
<div v-else aria-hidden="true" />
<!-- Filler : aligne la suite de la grille (Categorie au debut de ligne).
Inutile en consultation masquee (la grille se recompose sans les
champs vides, ERP-193). -->
<div v-else-if="!hideEmpty" aria-hidden="true" />
<MalioInputEmail
v-if="isBillingEmailRequired(model) && model.hasSecondaryBillingEmail"
@@ -181,7 +186,7 @@
/>
</div>
<div class="col-span-1">
<div v-if="!hideEmpty || isFilled(model.streetComplement)" class="col-span-1">
<MalioInputText
:model-value="model.streetComplement"
:label="t('commercial.clients.form.address.streetComplement')"
@@ -207,6 +212,7 @@ import { useAddressAutocomplete, type AddressSuggestion } from '~/shared/composa
import type { CategoryOption, RefOption } from '~/modules/commercial/composables/useClientReferentials'
import type { AddressFormDraft } from '~/modules/commercial/types/clientForm'
import { ADDRESS_MASK } from '~/shared/utils/textSanitize'
import { isFilled } from '~/shared/utils/consultationDisplay'
// Masque code postal FR : 5 chiffres.
const POSTAL_CODE_MASK = '#####'
@@ -227,6 +233,8 @@ const props = defineProps<{
readonly?: boolean
/** Bloc desactive (champs grises, consultation — distinct de readonly). */
disabled?: boolean
/** Consultation : masque les champs non remplis (ERP-193). */
hideEmpty?: boolean
/** Erreurs serveur 422 de cette ligne, indexees par champ (ERP-101). */
errors?: Record<string, string>
}>()
@@ -13,6 +13,7 @@
/>
<MalioInputText
v-if="!hideEmpty || isFilled(model.lastName)"
:model-value="model.lastName"
:label="t('commercial.clients.form.contact.lastName')"
:mask="PERSON_NAME_MASK"
@@ -22,6 +23,7 @@
@update:model-value="(v: string) => update('lastName', v)"
/>
<MalioInputText
v-if="!hideEmpty || isFilled(model.firstName)"
:model-value="model.firstName"
:label="t('commercial.clients.form.contact.firstName')"
:mask="PERSON_NAME_MASK"
@@ -33,7 +35,7 @@
<!-- Fonction sur 2 colonnes : on wrappe car MalioInputText
(inheritAttrs:false) renvoie `class` sur l'input interne, pas sur la
cellule de grille. Le wrapper porte le col-span-2, le champ le remplit. -->
<div class="col-span-2">
<div v-if="!hideEmpty || isFilled(model.jobTitle)" class="col-span-2">
<MalioInputText
:model-value="model.jobTitle"
:label="t('commercial.clients.form.contact.jobTitle')"
@@ -45,6 +47,7 @@
/>
</div>
<MalioInputEmail
v-if="!hideEmpty || isFilled(model.email)"
:model-value="model.email"
:label="t('commercial.clients.form.contact.email')"
:readonly="readonly"
@@ -54,6 +57,7 @@
@update:model-value="(v: string) => update('email', v)"
/>
<MalioInputPhone
v-if="!hideEmpty || isFilled(model.phonePrimary)"
:model-value="model.phonePrimary"
:label="t('commercial.clients.form.contact.phonePrimary')"
:mask="PHONE_MASK"
@@ -66,7 +70,7 @@
@add="revealSecondaryPhone"
/>
<MalioInputPhone
v-if="model.hasSecondaryPhone"
v-if="model.hasSecondaryPhone && (!hideEmpty || isFilled(model.phoneSecondary))"
:model-value="model.phoneSecondary"
:label="t('commercial.clients.form.contact.phoneSecondary')"
:mask="PHONE_MASK"
@@ -81,6 +85,7 @@
<script setup lang="ts">
import type { ContactFormDraft } from '~/modules/commercial/types/clientForm'
import { FREE_TEXT_MASK, PERSON_NAME_MASK } from '~/shared/utils/textSanitize'
import { isFilled } from '~/shared/utils/consultationDisplay'
// Masque telephone FR : 5 groupes de 2 chiffres (la normalisation finale reste
// serveur, cf. formatPhoneFR re-applique a la valeur renvoyee).
@@ -97,6 +102,8 @@ const props = defineProps<{
readonly?: boolean
/** Bloc desactive (champs grises, consultation — distinct de readonly). */
disabled?: boolean
/** Consultation : masque les champs non remplis (ERP-193). */
hideEmpty?: boolean
/** Erreurs serveur 422 de cette ligne, indexees par champ (ERP-101). */
errors?: Record<string, string>
}>()
@@ -40,6 +40,7 @@
<!-- Contacts rattaches (M2M, facultatif). -->
<MalioSelectCheckbox
v-if="!hideEmpty || isFilled(model.contactIris)"
:model-value="model.contactIris"
:options="contactOptions"
:label="t('commercial.suppliers.form.address.contacts')"
@@ -50,8 +51,9 @@
/>
<!-- Filler : aligne le debut de ligne suivant sur la grille (le bloc client
porte ici l'email de facturation, absent cote fournisseur). -->
<div aria-hidden="true" />
porte ici l'email de facturation, absent cote fournisseur). Inutile en
consultation masquee (la grille se recompose sans les champs vides). -->
<div v-if="!hideEmpty" aria-hidden="true" />
<!-- Categories de type FOURNISSEUR (>= 1 obligatoire, RG-2.10). -->
<MalioSelectCheckbox
@@ -145,7 +147,7 @@
/>
</div>
<div class="col-span-1">
<div v-if="!hideEmpty || isFilled(model.streetComplement)" class="col-span-1">
<MalioInputText
:model-value="model.streetComplement"
:label="t('commercial.suppliers.form.address.streetComplement')"
@@ -157,8 +159,10 @@
/>
</div>
<!-- Bennes : stepper (specifique fournisseur, defaut 0). -->
<!-- Bennes : stepper (specifique fournisseur, defaut 0). En consultation, 0
reste affiche (valeur saisie) ; seul un champ vide serait masque. -->
<MalioInputNumber
v-if="!hideEmpty || isFilled(model.bennes)"
:model-value="model.bennes"
:label="t('commercial.suppliers.form.address.bennes')"
:min="0"
@@ -168,8 +172,10 @@
@update:model-value="(v: string) => update('bennes', v)"
/>
<!-- Prestation de triage : booleen porte par l'adresse (specifique fournisseur). -->
<!-- Prestation de triage : booleen porte par l'adresse (specifique fournisseur).
Consultation : masquee si non cochee (ERP-193). -->
<MalioCheckbox
v-if="!hideEmpty || isFilled(model.triageProvider)"
id="address-triage-provider"
:label="t('commercial.suppliers.form.address.triageProvider')"
:model-value="model.triageProvider"
@@ -186,6 +192,7 @@ import { useAddressAutocomplete, type AddressSuggestion } from '~/shared/composa
import type { CategoryOption, RefOption } from '~/modules/commercial/composables/useSupplierReferentials'
import type { SupplierAddressFormDraft, SupplierAddressType } from '~/modules/commercial/types/supplierForm'
import { ADDRESS_MASK } from '~/shared/utils/textSanitize'
import { isFilled } from '~/shared/utils/consultationDisplay'
// Masque code postal FR : 5 chiffres.
const POSTAL_CODE_MASK = '#####'
@@ -206,6 +213,8 @@ const props = defineProps<{
readonly?: boolean
/** Bloc desactive (champs grises, consultation — distinct de readonly). */
disabled?: boolean
/** Consultation : masque les champs non remplis (ERP-193). */
hideEmpty?: boolean
/** Erreurs serveur 422 de cette ligne, indexees par champ (ERP-101). */
errors?: Record<string, string>
}>()
@@ -12,6 +12,7 @@
/>
<MalioInputText
v-if="!hideEmpty || isFilled(model.lastName)"
:model-value="model.lastName"
:label="t('commercial.suppliers.form.contact.lastName')"
:mask="PERSON_NAME_MASK"
@@ -21,6 +22,7 @@
@update:model-value="(v: string) => update('lastName', v)"
/>
<MalioInputText
v-if="!hideEmpty || isFilled(model.firstName)"
:model-value="model.firstName"
:label="t('commercial.suppliers.form.contact.firstName')"
:mask="PERSON_NAME_MASK"
@@ -32,7 +34,7 @@
<!-- Fonction sur 2 colonnes : on wrappe car MalioInputText
(inheritAttrs:false) renvoie `class` sur l'input interne, pas sur la
cellule de grille. Le wrapper porte le col-span-2, le champ le remplit. -->
<div class="col-span-2">
<div v-if="!hideEmpty || isFilled(model.jobTitle)" class="col-span-2">
<MalioInputText
:model-value="model.jobTitle"
:label="t('commercial.suppliers.form.contact.jobTitle')"
@@ -44,6 +46,7 @@
/>
</div>
<MalioInputEmail
v-if="!hideEmpty || isFilled(model.email)"
:model-value="model.email"
:label="t('commercial.suppliers.form.contact.email')"
:readonly="readonly"
@@ -53,6 +56,7 @@
@update:model-value="(v: string) => update('email', v)"
/>
<MalioInputPhone
v-if="!hideEmpty || isFilled(model.phonePrimary)"
:model-value="model.phonePrimary"
:label="t('commercial.suppliers.form.contact.phonePrimary')"
:mask="PHONE_MASK"
@@ -65,7 +69,7 @@
@add="revealSecondaryPhone"
/>
<MalioInputPhone
v-if="model.hasSecondaryPhone"
v-if="model.hasSecondaryPhone && (!hideEmpty || isFilled(model.phoneSecondary))"
:model-value="model.phoneSecondary"
:label="t('commercial.suppliers.form.contact.phoneSecondary')"
:mask="PHONE_MASK"
@@ -80,6 +84,7 @@
<script setup lang="ts">
import type { SupplierContactFormDraft } from '~/modules/commercial/types/supplierForm'
import { FREE_TEXT_MASK, PERSON_NAME_MASK } from '~/shared/utils/textSanitize'
import { isFilled } from '~/shared/utils/consultationDisplay'
// Masque telephone FR : 5 groupes de 2 chiffres (la normalisation finale reste serveur).
const PHONE_MASK = '## ## ## ## ##'
@@ -95,6 +100,8 @@ const props = defineProps<{
readonly?: boolean
/** Bloc desactive (champs grises, consultation — distinct de readonly). */
disabled?: boolean
/** Consultation : masque les champs non remplis (ERP-193). */
hideEmpty?: boolean
/** Erreurs serveur 422 de cette ligne, indexees par champ (ERP-101). */
errors?: Record<string, string>
}>()
@@ -49,19 +49,23 @@
<!-- Formulaire principal (lecture seule) -->
<div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
v-if="isFilled(client.companyName)"
:model-value="client.companyName"
:label="t('commercial.clients.form.main.companyName')"
disabled
/>
<MalioSelectCheckbox
v-if="isFilled(categoryIris)"
:model-value="categoryIris"
:options="mainCategoryOptions"
:label="t('commercial.clients.form.main.categories')"
:display-tag="true"
disabled
/>
<!-- Relation toujours affichee (vide = « Aucun »), comme en edition. -->
<!-- Relation : masquee en consultation si aucune (ERP-193) ; en edition
elle reste toujours visible (vide = « Aucun »). -->
<MalioSelect
v-if="isFilled(relation.type)"
:model-value="relation.type"
:options="relationOptions"
:label="t('commercial.clients.form.main.relation')"
@@ -71,12 +75,14 @@
<!-- Nom du distributeur/courtier : conditionnel (libelle type-dependant,
aucune valeur sans relation meme comportement qu'en edition). -->
<MalioInputText
v-if="relation.type"
v-if="relation.type && isFilled(relation.name)"
:model-value="relation.name"
:label="relation.type === 'distributeur' ? t('commercial.clients.form.main.distributorName') : t('commercial.clients.form.main.brokerName')"
disabled
/>
<!-- Service de triage : case a cocher masquee si non cochee (ERP-193). -->
<MalioCheckbox
v-if="isFilled(client.triageService === true)"
:model-value="client.triageService === true"
:label="t('commercial.clients.form.main.triageService')"
group-class="self-center"
@@ -95,6 +101,7 @@
sur les inputs (champ 40px centre dans un h-12 -> ~4px de
coussin de chaque cote). -->
<MalioInputTextArea
v-if="isFilled(information.description)"
:model-value="information.description"
:label="t('commercial.clients.form.information.description')"
resize="none"
@@ -103,31 +110,37 @@
disabled
/>
<MalioInputText
v-if="isFilled(information.competitors)"
:model-value="information.competitors"
:label="t('commercial.clients.form.information.competitors')"
disabled
/>
<MalioDate
v-if="isFilled(information.foundedAt)"
:model-value="information.foundedAt"
:label="t('commercial.clients.form.information.foundedAt')"
disabled
/>
<MalioInputText
v-if="isFilled(information.employeesCount)"
:model-value="information.employeesCount"
:label="t('commercial.clients.form.information.employeesCount')"
disabled
/>
<MalioInputAmount
v-if="isFilled(information.revenueAmount)"
:model-value="information.revenueAmount"
:label="t('commercial.clients.form.information.revenueAmount')"
disabled
/>
<MalioInputText
v-if="isFilled(information.directorName)"
:model-value="information.directorName"
:label="t('commercial.clients.form.information.directorName')"
disabled
/>
<MalioInputAmount
v-if="isFilled(information.profitAmount)"
:model-value="information.profitAmount"
:label="t('commercial.clients.form.information.profitAmount')"
disabled
@@ -144,6 +157,7 @@
:model-value="contact"
:title="t('commercial.clients.form.contact.title', { n: index + 1 })"
disabled
hide-empty
/>
</div>
</template>
@@ -161,6 +175,7 @@
:contact-options="contactOptions"
:country-options="countryOptions"
disabled
hide-empty
/>
</div>
</template>
@@ -171,17 +186,20 @@
<div class="bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
v-if="isFilled(accounting.siren)"
:model-value="accounting.siren"
:label="t('commercial.clients.form.accounting.siren')"
:mask="SIREN_MASK"
disabled
/>
<MalioInputText
v-if="isFilled(accounting.accountNumber)"
:model-value="accounting.accountNumber"
:label="t('commercial.clients.form.accounting.accountNumber')"
disabled
/>
<MalioSelect
v-if="isFilled(accounting.tvaModeIri)"
:model-value="accounting.tvaModeIri"
:options="tvaModeOptions"
:label="t('commercial.clients.form.accounting.tvaMode')"
@@ -189,11 +207,13 @@
disabled
/>
<MalioInputText
v-if="isFilled(accounting.nTva)"
:model-value="accounting.nTva"
:label="t('commercial.clients.form.accounting.nTva')"
disabled
/>
<MalioSelect
v-if="isFilled(accounting.paymentDelayIri)"
:model-value="accounting.paymentDelayIri"
:options="paymentDelayOptions"
:label="t('commercial.clients.form.accounting.paymentDelay')"
@@ -201,6 +221,7 @@
disabled
/>
<MalioSelect
v-if="isFilled(accounting.paymentTypeIri)"
:model-value="accounting.paymentTypeIri"
:options="paymentTypeOptions"
:label="t('commercial.clients.form.accounting.paymentType')"
@@ -226,16 +247,19 @@
>
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
v-if="isFilled(rib.label)"
:model-value="rib.label"
:label="t('commercial.clients.form.accounting.ribLabel')"
disabled
/>
<MalioInputText
v-if="isFilled(rib.bic)"
:model-value="rib.bic"
:label="t('commercial.clients.form.accounting.ribBic')"
disabled
/>
<MalioInputText
v-if="isFilled(rib.iban)"
:model-value="rib.iban"
:label="t('commercial.clients.form.accounting.ribIban')"
disabled
@@ -300,6 +324,7 @@ import {
type SelectOption,
} from '~/modules/commercial/utils/forms/clientConsultation'
import { emptyAddress, emptyContact } from '~/modules/commercial/types/clientForm'
import { isFilled } from '~/shared/utils/consultationDisplay'
// Masque d'affichage (purement visuel, la donnee reste celle du serveur).
const SIREN_MASK = '#########'
@@ -49,11 +49,13 @@
<!-- Formulaire principal (lecture seule) -->
<div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
v-if="isFilled(supplier.companyName)"
:model-value="supplier.companyName"
:label="t('commercial.suppliers.form.main.companyName')"
disabled
/>
<MalioSelectCheckbox
v-if="isFilled(categoryIris)"
:model-value="categoryIris"
:options="mainCategoryOptions"
:label="t('commercial.suppliers.form.main.categories')"
@@ -73,6 +75,7 @@
<!-- pt-1/pb-1 alignent le textarea (h-full) en haut ET en bas
sur les inputs (champ 40px centre dans un h-12). -->
<MalioInputTextArea
v-if="isFilled(information.description)"
:model-value="information.description"
:label="t('commercial.suppliers.form.information.description')"
resize="none"
@@ -81,37 +84,44 @@
disabled
/>
<MalioInputText
v-if="isFilled(information.competitors)"
:model-value="information.competitors"
:label="t('commercial.suppliers.form.information.competitors')"
disabled
/>
<MalioDate
v-if="isFilled(information.foundedAt)"
:model-value="information.foundedAt"
:label="t('commercial.suppliers.form.information.foundedAt')"
disabled
/>
<MalioInputText
v-if="isFilled(information.employeesCount)"
:model-value="information.employeesCount"
:label="t('commercial.suppliers.form.information.employeesCount')"
disabled
/>
<MalioInputAmount
v-if="isFilled(information.revenueAmount)"
:model-value="information.revenueAmount"
:label="t('commercial.suppliers.form.information.revenueAmount')"
disabled
/>
<MalioInputText
v-if="isFilled(information.directorName)"
:model-value="information.directorName"
:label="t('commercial.suppliers.form.information.directorName')"
disabled
/>
<MalioInputAmount
v-if="isFilled(information.profitAmount)"
:model-value="information.profitAmount"
:label="t('commercial.suppliers.form.information.profitAmount')"
disabled
/>
<!-- Volume previsionnel : specifique fournisseur (entier). -->
<MalioInputText
v-if="isFilled(information.volumeForecast)"
:model-value="information.volumeForecast"
:label="t('commercial.suppliers.form.information.volumeForecast')"
disabled
@@ -128,6 +138,7 @@
:model-value="contact"
:title="t('commercial.suppliers.form.contact.title', { n: index + 1 })"
disabled
hide-empty
/>
</div>
</template>
@@ -145,6 +156,7 @@
:contact-options="contactOptions"
:country-options="countryOptions"
disabled
hide-empty
/>
</div>
</template>
@@ -155,17 +167,20 @@
<div class="bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
v-if="isFilled(accounting.siren)"
:model-value="accounting.siren"
:label="t('commercial.suppliers.form.accounting.siren')"
:mask="SIREN_MASK"
disabled
/>
<MalioInputText
v-if="isFilled(accounting.accountNumber)"
:model-value="accounting.accountNumber"
:label="t('commercial.suppliers.form.accounting.accountNumber')"
disabled
/>
<MalioSelect
v-if="isFilled(accounting.tvaModeIri)"
:model-value="accounting.tvaModeIri"
:options="tvaModeOptions"
:label="t('commercial.suppliers.form.accounting.tvaMode')"
@@ -173,11 +188,13 @@
disabled
/>
<MalioInputText
v-if="isFilled(accounting.nTva)"
:model-value="accounting.nTva"
:label="t('commercial.suppliers.form.accounting.nTva')"
disabled
/>
<MalioSelect
v-if="isFilled(accounting.paymentDelayIri)"
:model-value="accounting.paymentDelayIri"
:options="paymentDelayOptions"
:label="t('commercial.suppliers.form.accounting.paymentDelay')"
@@ -185,6 +202,7 @@
disabled
/>
<MalioSelect
v-if="isFilled(accounting.paymentTypeIri)"
:model-value="accounting.paymentTypeIri"
:options="paymentTypeOptions"
:label="t('commercial.suppliers.form.accounting.paymentType')"
@@ -210,16 +228,19 @@
>
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
v-if="isFilled(rib.label)"
:model-value="rib.label"
:label="t('commercial.suppliers.form.accounting.ribLabel')"
disabled
/>
<MalioInputText
v-if="isFilled(rib.bic)"
:model-value="rib.bic"
:label="t('commercial.suppliers.form.accounting.ribBic')"
disabled
/>
<MalioInputText
v-if="isFilled(rib.iban)"
:model-value="rib.iban"
:label="t('commercial.suppliers.form.accounting.ribIban')"
disabled
@@ -284,6 +305,7 @@ import {
type SupplierDetail,
} from '~/modules/commercial/utils/forms/supplierConsultation'
import { emptyContact } from '~/modules/commercial/types/supplierForm'
import { isFilled } from '~/shared/utils/consultationDisplay'
// Masque d'affichage (purement visuel, la donnee reste celle du serveur).
const SIREN_MASK = '#########'
@@ -12,6 +12,7 @@
<!-- Sites Starseed : multiselect a tags (>= 1 obligatoire, RG-3.05). -->
<MalioSelectCheckbox
v-if="!hideEmpty || isFilled(model.siteIris)"
:model-value="model.siteIris"
:options="siteOptions"
:label="t('technique.providers.form.address.sites')"
@@ -25,6 +26,7 @@
<!-- Contacts rattaches (M2M, facultatif) : alimente par l'onglet Contact. -->
<MalioSelectCheckbox
v-if="!hideEmpty || isFilled(model.contactIris)"
:model-value="model.contactIris"
:options="contactOptions"
:label="t('technique.providers.form.address.contacts')"
@@ -35,6 +37,7 @@
/>
<MalioSelect
v-if="!hideEmpty || isFilled(model.country)"
:model-value="model.country"
:options="countryOptions"
:label="t('technique.providers.form.address.country')"
@@ -45,6 +48,7 @@
/>
<MalioInputText
v-if="!hideEmpty || isFilled(model.postalCode)"
:model-value="model.postalCode"
:label="t('technique.providers.form.address.postalCode')"
:mask="POSTAL_CODE_MASK"
@@ -57,7 +61,7 @@
<!-- Ville : MalioSelect alimente par le code postal (BAN). Saisie libre si BAN indispo. -->
<MalioSelect
v-if="!degraded"
v-if="!degraded && (!hideEmpty || isFilled(model.city))"
:model-value="model.city"
:options="cityOptions"
:label="t('technique.providers.form.address.city')"
@@ -69,7 +73,7 @@
@update:model-value="onCityChange"
/>
<MalioInputText
v-else
v-else-if="degraded && (!hideEmpty || isFilled(model.city))"
:model-value="model.city"
:label="t('technique.providers.form.address.city')"
:mask="ADDRESS_MASK"
@@ -82,7 +86,7 @@
<!-- Adresse (BAN) sur 2 colonnes + Adresse complementaire. allow-create : le
texte saisi est conserve si la BAN ne propose rien (saisie manuelle). -->
<div class="col-span-2">
<div v-if="!hideEmpty || isFilled(model.street)" class="col-span-2">
<MalioInputAutocomplete
v-if="!readonly && !disabled"
:model-value="model.street"
@@ -112,7 +116,7 @@
/>
</div>
<div class="col-span-1">
<div v-if="!hideEmpty || isFilled(model.streetComplement)" class="col-span-1">
<MalioInputText
:model-value="model.streetComplement"
:label="t('technique.providers.form.address.streetComplement')"
@@ -131,6 +135,7 @@ import { useAddressAutocomplete, type AddressSuggestion } from '~/shared/composa
import type { RefOption } from '~/modules/technique/composables/useProviderReferentials'
import type { ProviderAddressFormDraft } from '~/modules/technique/types/providerForm'
import { ADDRESS_MASK } from '~/shared/utils/textSanitize'
import { isFilled } from '~/shared/utils/consultationDisplay'
// Masque code postal FR : 5 chiffres.
const POSTAL_CODE_MASK = '#####'
@@ -148,6 +153,8 @@ const props = defineProps<{
readonly?: boolean
/** Bloc desactive (champs grises, consultation — distinct de readonly). */
disabled?: boolean
/** Consultation : masque les champs non remplis (ERP-193). */
hideEmpty?: boolean
/** Erreurs serveur 422 de cette ligne, indexees par champ (ERP-101). */
errors?: Record<string, string>
}>()
@@ -12,6 +12,7 @@
/>
<MalioInputText
v-if="!hideEmpty || isFilled(model.lastName)"
:model-value="model.lastName"
:label="t('technique.providers.form.contact.lastName')"
:mask="PERSON_NAME_MASK"
@@ -21,6 +22,7 @@
@update:model-value="(v: string) => update('lastName', v)"
/>
<MalioInputText
v-if="!hideEmpty || isFilled(model.firstName)"
:model-value="model.firstName"
:label="t('technique.providers.form.contact.firstName')"
:mask="PERSON_NAME_MASK"
@@ -32,7 +34,7 @@
<!-- Fonction sur 2 colonnes : on wrappe car MalioInputText
(inheritAttrs:false) renvoie `class` sur l'input interne, pas sur la
cellule de grille. Le wrapper porte le col-span-2, le champ le remplit. -->
<div class="col-span-2">
<div v-if="!hideEmpty || isFilled(model.jobTitle)" class="col-span-2">
<MalioInputText
:model-value="model.jobTitle"
:label="t('technique.providers.form.contact.jobTitle')"
@@ -44,6 +46,7 @@
/>
</div>
<MalioInputEmail
v-if="!hideEmpty || isFilled(model.email)"
:model-value="model.email"
:label="t('technique.providers.form.contact.email')"
:readonly="readonly"
@@ -53,6 +56,7 @@
@update:model-value="(v: string) => update('email', v)"
/>
<MalioInputPhone
v-if="!hideEmpty || isFilled(model.phonePrimary)"
:model-value="model.phonePrimary"
:label="t('technique.providers.form.contact.phonePrimary')"
:mask="PHONE_MASK"
@@ -66,7 +70,7 @@
/>
<!-- 2e numero : revele a la demande (max 2 telephones par contact). -->
<MalioInputPhone
v-if="model.hasSecondaryPhone"
v-if="model.hasSecondaryPhone && (!hideEmpty || isFilled(model.phoneSecondary))"
:model-value="model.phoneSecondary"
:label="t('technique.providers.form.contact.phoneSecondary')"
:mask="PHONE_MASK"
@@ -81,6 +85,7 @@
<script setup lang="ts">
import type { ProviderContactFormDraft } from '~/modules/technique/types/providerForm'
import { FREE_TEXT_MASK, PERSON_NAME_MASK } from '~/shared/utils/textSanitize'
import { isFilled } from '~/shared/utils/consultationDisplay'
// Masque telephone FR : 5 groupes de 2 chiffres (la normalisation finale reste serveur).
const PHONE_MASK = '## ## ## ## ##'
@@ -94,6 +99,8 @@ const props = defineProps<{
readonly?: boolean
/** Bloc desactive (champs grises, consultation — distinct de readonly). */
disabled?: boolean
/** Consultation : masque les champs non remplis (ERP-193). */
hideEmpty?: boolean
/** Erreurs serveur 422 de cette ligne, indexees par champ (ERP-101). */
errors?: Record<string, string>
}>()
@@ -48,11 +48,13 @@
<!-- Bloc principal (lecture seule) -->
<div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
v-if="isFilled(provider.companyName)"
:model-value="provider.companyName"
:label="t('technique.providers.form.main.companyName')"
disabled
/>
<MalioSelectCheckbox
v-if="isFilled(mainCategoryIris)"
:model-value="mainCategoryIris"
:options="mainCategoryOptions"
:label="t('technique.providers.form.main.categories')"
@@ -60,6 +62,7 @@
disabled
/>
<MalioSelectCheckbox
v-if="isFilled(mainSiteIris)"
:model-value="mainSiteIris"
:options="mainSiteOptions"
:label="t('technique.providers.form.main.sites')"
@@ -79,6 +82,7 @@
:key="index"
:model-value="contact"
disabled
hide-empty
/>
</div>
</template>
@@ -94,6 +98,7 @@
:contact-options="contactOptions"
:country-options="countryOptionsFor(view.draft.country)"
disabled
hide-empty
/>
</div>
</template>
@@ -105,13 +110,13 @@
<div class="mt-12 flex flex-col gap-6">
<div class="bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText :model-value="accounting.siren" :label="t('technique.providers.form.accounting.siren')" disabled />
<MalioInputText :model-value="accounting.accountNumber" :label="t('technique.providers.form.accounting.accountNumber')" disabled />
<MalioSelect :model-value="accounting.tvaModeIri" :options="tvaModeOptions" :label="t('technique.providers.form.accounting.tvaMode')" disabled empty-option-label="" />
<MalioInputText :model-value="accounting.nTva" :label="t('technique.providers.form.accounting.nTva')" disabled />
<MalioSelect :model-value="accounting.paymentDelayIri" :options="paymentDelayOptions" :label="t('technique.providers.form.accounting.paymentDelay')" disabled empty-option-label="" />
<MalioSelect :model-value="accounting.paymentTypeIri" :options="paymentTypeOptions" :label="t('technique.providers.form.accounting.paymentType')" disabled empty-option-label="" />
<MalioSelect v-if="isBankRequired" :model-value="accounting.bankIri" :options="bankOptions" :label="t('technique.providers.form.accounting.bank')" disabled empty-option-label="" />
<MalioInputText v-if="isFilled(accounting.siren)" :model-value="accounting.siren" :label="t('technique.providers.form.accounting.siren')" disabled />
<MalioInputText v-if="isFilled(accounting.accountNumber)" :model-value="accounting.accountNumber" :label="t('technique.providers.form.accounting.accountNumber')" disabled />
<MalioSelect v-if="isFilled(accounting.tvaModeIri)" :model-value="accounting.tvaModeIri" :options="tvaModeOptions" :label="t('technique.providers.form.accounting.tvaMode')" disabled empty-option-label="" />
<MalioInputText v-if="isFilled(accounting.nTva)" :model-value="accounting.nTva" :label="t('technique.providers.form.accounting.nTva')" disabled />
<MalioSelect v-if="isFilled(accounting.paymentDelayIri)" :model-value="accounting.paymentDelayIri" :options="paymentDelayOptions" :label="t('technique.providers.form.accounting.paymentDelay')" disabled empty-option-label="" />
<MalioSelect v-if="isFilled(accounting.paymentTypeIri)" :model-value="accounting.paymentTypeIri" :options="paymentTypeOptions" :label="t('technique.providers.form.accounting.paymentType')" disabled empty-option-label="" />
<MalioSelect v-if="isBankRequired && isFilled(accounting.bankIri)" :model-value="accounting.bankIri" :options="bankOptions" :label="t('technique.providers.form.accounting.bank')" disabled empty-option-label="" />
</div>
</div>
@@ -122,9 +127,9 @@
class="bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
>
<div class="grid grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText :model-value="rib.label" :label="t('technique.providers.form.accounting.ribLabel')" disabled />
<MalioInputText :model-value="rib.bic" :label="t('technique.providers.form.accounting.ribBic')" disabled />
<MalioInputText :model-value="rib.iban" :label="t('technique.providers.form.accounting.ribIban')" disabled />
<MalioInputText v-if="isFilled(rib.label)" :model-value="rib.label" :label="t('technique.providers.form.accounting.ribLabel')" disabled />
<MalioInputText v-if="isFilled(rib.bic)" :model-value="rib.bic" :label="t('technique.providers.form.accounting.ribBic')" disabled />
<MalioInputText v-if="isFilled(rib.iban)" :model-value="rib.iban" :label="t('technique.providers.form.accounting.ribIban')" disabled />
</div>
</div>
</div>
@@ -177,6 +182,7 @@ import {
} from '~/modules/technique/utils/forms/providerDetail'
import { isBankRequiredForPaymentType, isRibRequiredForPaymentType } from '~/modules/technique/utils/forms/providerAccounting'
import { emptyProviderAddress, emptyProviderContact } from '~/modules/technique/types/providerForm'
import { isFilled } from '~/shared/utils/consultationDisplay'
const { t } = useI18n()
const route = useRoute()
@@ -3,6 +3,7 @@
<div class="relative grid grid-cols-4 gap-x-[44px] gap-y-4 bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]">
<!-- Pays : prerempli « France » (RG-4.05). -->
<MalioSelect
v-if="!hideEmpty || isFilled(model.country)"
:model-value="model.country"
:options="countryOptions"
:label="t('transport.carriers.form.address.country')"
@@ -15,6 +16,7 @@
<!-- Code postal (RG-4.06) : declenche l'autocomplete ville (BAN). -->
<MalioInputText
v-if="!hideEmpty || isFilled(model.postalCode)"
:model-value="model.postalCode"
:label="t('transport.carriers.form.address.postalCode')"
:mask="POSTAL_CODE_MASK"
@@ -27,7 +29,7 @@
<!-- Ville : MalioSelect alimente par le code postal (BAN). Saisie libre si BAN indispo. -->
<MalioSelect
v-if="!degraded"
v-if="!degraded && (!hideEmpty || isFilled(model.city))"
:model-value="model.city"
:options="cityOptions"
:label="t('transport.carriers.form.address.city')"
@@ -39,7 +41,7 @@
@update:model-value="onCityChange"
/>
<MalioInputText
v-else
v-else-if="degraded && (!hideEmpty || isFilled(model.city))"
:model-value="model.city"
:label="t('transport.carriers.form.address.city')"
:mask="ADDRESS_MASK"
@@ -50,12 +52,13 @@
@update:model-value="(v: string) => update('city', v)"
/>
<!-- Filler : aligne le debut de la ligne suivante sur la grille. -->
<div aria-hidden="true" />
<!-- Filler : aligne le debut de la ligne suivante sur la grille. Inutile en
consultation masquee (la grille se recompose sans les champs vides). -->
<div v-if="!hideEmpty" aria-hidden="true" />
<!-- Adresse (BAN) sur 2 colonnes + Adresse complementaire. allow-create : le
texte saisi est conserve si la BAN ne propose rien (saisie manuelle). -->
<div class="col-span-2">
<div v-if="!hideEmpty || isFilled(model.street)" class="col-span-2">
<MalioInputAutocomplete
v-if="!readonly && !disabled"
:model-value="model.street"
@@ -86,6 +89,7 @@
</div>
<MalioInputText
v-if="!hideEmpty || isFilled(model.streetComplement)"
:model-value="model.streetComplement"
:label="t('transport.carriers.form.address.streetComplement')"
:mask="ADDRESS_MASK"
@@ -101,6 +105,7 @@
import { useAddressAutocomplete, type AddressSuggestion } from '~/shared/composables/useAddressAutocomplete'
import type { CarrierAddressFormDraft } from '~/modules/transport/types/carrierForm'
import { ADDRESS_MASK } from '~/shared/utils/textSanitize'
import { isFilled } from '~/shared/utils/consultationDisplay'
interface RefOption {
value: string
@@ -118,6 +123,8 @@ const props = defineProps<{
readonly?: boolean
/** Bloc desactive (champs grises, consultation — distinct de readonly). */
disabled?: boolean
/** Consultation : masque les champs non remplis (ERP-193). */
hideEmpty?: boolean
/** Erreurs serveur 422 de cette ligne, indexees par champ (ERP-101). */
errors?: Record<string, string>
}>()
@@ -12,6 +12,7 @@
/>
<MalioInputText
v-if="!hideEmpty || isFilled(model.lastName)"
:model-value="model.lastName"
:label="t('transport.carriers.form.contact.lastName')"
:mask="PERSON_NAME_MASK"
@@ -21,6 +22,7 @@
@update:model-value="(v: string) => update('lastName', v)"
/>
<MalioInputText
v-if="!hideEmpty || isFilled(model.firstName)"
:model-value="model.firstName"
:label="t('transport.carriers.form.contact.firstName')"
:mask="PERSON_NAME_MASK"
@@ -31,7 +33,7 @@
/>
<!-- Fonction sur 2 colonnes : on wrappe car MalioInputText (inheritAttrs:false)
renvoie `class` sur l'input interne, pas sur la cellule de grille. -->
<div class="col-span-2">
<div v-if="!hideEmpty || isFilled(model.jobTitle)" class="col-span-2">
<MalioInputText
:model-value="model.jobTitle"
:label="t('transport.carriers.form.contact.jobTitle')"
@@ -43,6 +45,7 @@
/>
</div>
<MalioInputEmail
v-if="!hideEmpty || isFilled(model.email)"
:model-value="model.email"
:label="t('transport.carriers.form.contact.email')"
:readonly="readonly"
@@ -53,6 +56,7 @@
/>
<!-- Téléphone principal + bouton « + » révélant le 2e numéro (max 2). -->
<MalioInputPhone
v-if="!hideEmpty || isFilled(model.phonePrimary)"
:model-value="model.phonePrimary"
:label="t('transport.carriers.form.contact.phonePrimary')"
:mask="PHONE_MASK"
@@ -66,7 +70,7 @@
/>
<!-- 2e numéro : révélé à la demande (max 2 téléphones — RG-4.08). -->
<MalioInputPhone
v-if="model.hasSecondaryPhone"
v-if="model.hasSecondaryPhone && (!hideEmpty || isFilled(model.phoneSecondary))"
:model-value="model.phoneSecondary"
:label="t('transport.carriers.form.contact.phoneSecondary')"
:mask="PHONE_MASK"
@@ -81,6 +85,7 @@
<script setup lang="ts">
import type { CarrierContactFormDraft } from '~/modules/transport/types/carrierForm'
import { FREE_TEXT_MASK, PERSON_NAME_MASK } from '~/shared/utils/textSanitize'
import { isFilled } from '~/shared/utils/consultationDisplay'
// Masque téléphone FR : 5 groupes de 2 chiffres (la normalisation finale reste serveur).
const PHONE_MASK = '## ## ## ## ##'
@@ -94,6 +99,8 @@ const props = defineProps<{
readonly?: boolean
/** Bloc desactive (champs grises, consultation — distinct de readonly). */
disabled?: boolean
/** Consultation : masque les champs non remplis (ERP-193). */
hideEmpty?: boolean
/** Erreurs serveur 422 de cette ligne, indexées par champ (ERP-101). */
errors?: Record<string, string>
}>()
@@ -46,11 +46,11 @@
<template v-else-if="carrier">
<!-- Bloc principal (lecture seule) même disposition que l'ajout -->
<div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText :model-value="main.name" :label="t('transport.carriers.form.main.name')" disabled />
<MalioInputText v-if="isFilled(main.name)" :model-value="main.name" :label="t('transport.carriers.form.main.name')" disabled />
<!-- Cas LIOT : le champ immatriculations occupe les colonnes restantes
de la ligne (3 en xl, 2 sinon), comme à l'ajout / la modification. -->
<div v-if="isLiot" class="col-span-2 xl:col-span-3">
<div v-if="isLiot && isFilled(main.liotPlates)" class="col-span-2 xl:col-span-3">
<MalioInputText
:model-value="main.liotPlates"
:label="t('transport.carriers.form.main.liotPlates')"
@@ -61,22 +61,22 @@
<!-- Cas standard : certification + décharge (col 3 réservée) + affrètement (col 4). -->
<template v-if="!isLiot">
<MalioInputText
v-if="isFilled(certificationLabel)"
:model-value="certificationLabel"
:label="t('transport.carriers.form.main.certificationType')"
disabled
/>
<!-- Colonne 3 réservée à la décharge (si AUTRE), sinon vide (xl). -->
<!-- Décharge (si AUTRE) : affichee uniquement si un document est attache. -->
<MalioInputText
v-if="main.certificationType === 'AUTRE'"
v-if="main.certificationType === 'AUTRE' && isFilled(dischargeLabel)"
:model-value="dischargeLabel"
:label="t('transport.carriers.form.main.discharge')"
disabled
/>
<div v-else class="hidden xl:block"></div>
<!-- Affréter : colonne 4, centré (h-12) comme à l'ajout. -->
<div class="flex h-12 items-center">
<!-- Affréter : masquee si non cochee (ERP-193). -->
<div v-if="isFilled(main.isChartered)" class="flex h-12 items-center">
<MalioCheckbox
id="carrier-view-chartered"
:label="t('transport.carriers.form.main.isChartered')"
@@ -88,9 +88,9 @@
<!-- Champs d'affrètement (ligne 2) si affrété. -->
<template v-if="main.isChartered">
<MalioInputText :model-value="indexationDisplay" :label="t('transport.carriers.form.main.indexationRate')" disabled />
<MalioInputText v-if="isFilled(indexationDisplay)" :model-value="indexationDisplay" :label="t('transport.carriers.form.main.indexationRate')" disabled />
<!-- Contenant : radios désactivés (lecture seule), aligné sur l'ajout / la modif. -->
<div>
<div v-if="isFilled(main.containerType)">
<div class="flex h-12 items-center gap-4">
<MalioRadioButton
:model-value="main.containerType"
@@ -110,7 +110,7 @@
/>
</div>
</div>
<MalioInputText :model-value="main.volumeM3" :label="t('transport.carriers.form.main.volumeM3')" disabled />
<MalioInputText v-if="isFilled(main.volumeM3)" :model-value="main.volumeM3" :label="t('transport.carriers.form.main.volumeM3')" disabled />
</template>
</template>
</div>
@@ -125,6 +125,7 @@
:model-value="address"
:country-options="countryOptionsFor(address.country)"
disabled
hide-empty
/>
</div>
</template>
@@ -136,6 +137,7 @@
:key="index"
:model-value="contact"
disabled
hide-empty
/>
</div>
</template>
@@ -256,6 +258,7 @@ import {
type Relation,
} from '~/modules/transport/utils/forms/carrierMappers'
import { extractApiErrorMessage } from '~/shared/utils/api'
import { isFilled } from '~/shared/utils/consultationDisplay'
interface SelectOption {
value: string
@@ -0,0 +1,23 @@
import { describe, it, expect } from 'vitest'
import { isFilled } from '../consultationDisplay'
describe('isFilled (masquage des champs vides en consultation, ERP-193)', () => {
it('considere VIDE : null / undefined / chaine vide ou espaces / tableau vide / false', () => {
expect(isFilled(null)).toBe(false)
expect(isFilled(undefined)).toBe(false)
expect(isFilled('')).toBe(false)
expect(isFilled(' ')).toBe(false)
expect(isFilled([])).toBe(false)
// Case a cocher non cochee => masquee.
expect(isFilled(false)).toBe(false)
})
it('considere REMPLI : chaine non vide / tableau non vide / nombre (y compris 0) / true / objet', () => {
expect(isFilled('Dupont')).toBe(true)
expect(isFilled(['/api/sites/1'])).toBe(true)
expect(isFilled(0)).toBe(true)
expect(isFilled(42)).toBe(true)
expect(isFilled(true)).toBe(true)
expect(isFilled({ '@id': '/api/x/1' })).toBe(true)
})
})
@@ -0,0 +1,37 @@
/**
* Helpers d'affichage en CONSULTATION (lecture seule).
*
* Decision metier (ERP-193) : en consultation, on masque les champs non remplis
* (et les cases a cocher non cochees) pour ne montrer que l'information saisie.
* Mutualise entre modules (fournisseur, prestataire, client, transporteur) : la
* meme regle « vide » s'applique partout.
*/
/**
* Indique si une valeur est « remplie » (donc a afficher en consultation).
*
* Sont consideres VIDES (donc masques) :
* - null / undefined
* - chaine vide ou composee uniquement d'espaces
* - tableau vide (multiselect / cases a cocher sans selection)
* - booleen `false` (case a cocher non cochee)
*
* Restent AFFICHES : tout nombre (y compris 0, qui est une valeur saisie), les
* objets non nuls, et toute chaine non vide.
*/
export function isFilled(value: unknown): boolean {
if (value === null || value === undefined) {
return false
}
if (typeof value === 'string') {
return value.trim() !== ''
}
if (Array.isArray(value)) {
return value.length > 0
}
if (typeof value === 'boolean') {
return value
}
return true
}