refactor(front) : champs anti-parasites via masks maska (filtrage natif, focus/curseur OK) au lieu du sanitizer @update ; email sans masque (ERP-193)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Has been cancelled
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Has been cancelled

This commit is contained in:
2026-06-19 14:25:26 +02:00
parent e66615d40b
commit c11d7822ce
18 changed files with 229 additions and 290 deletions
@@ -24,8 +24,8 @@
`manage` (ex. Compta). -->
<div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
:model-value="main.companyName"
@update:model-value="(v: string) => main.companyName = sanitizeFreeText(v)"
v-model="main.companyName"
:mask="FREE_TEXT_MASK"
:label="t('commercial.clients.form.main.companyName')"
:required="true"
:disabled="businessReadonly"
@@ -106,8 +106,8 @@
:error="informationErrors.errors.description"
/>
<MalioInputText
:model-value="information.competitors"
@update:model-value="(v: string) => information.competitors = sanitizeFreeText(v)"
v-model="information.competitors"
:mask="FREE_TEXT_MASK"
:label="t('commercial.clients.form.information.competitors')"
:disabled="businessReadonly"
:error="informationErrors.errors.competitors"
@@ -141,8 +141,8 @@
@update:model-value="onRevenueAmountInput"
/>
<MalioInputText
:model-value="information.directorName"
@update:model-value="(v: string) => information.directorName = sanitizePersonName(v)"
v-model="information.directorName"
:mask="PERSON_NAME_MASK"
:label="t('commercial.clients.form.information.directorName')"
:disabled="businessReadonly"
:error="informationErrors.errors.directorName"
@@ -254,8 +254,8 @@
:error="accountingErrors.errors.siren"
/>
<MalioInputText
:model-value="accounting.accountNumber"
@update:model-value="(v: string) => accounting.accountNumber = sanitizeCodeAlnum(v)"
v-model="accounting.accountNumber"
:mask="CODE_ALNUM_MASK"
:label="t('commercial.clients.form.accounting.accountNumber')"
:disabled="accountingReadonly"
:required="true"
@@ -272,8 +272,8 @@
@update:model-value="(v: string | number | null) => accounting.tvaModeIri = v === null ? null : String(v)"
/>
<MalioInputText
:model-value="accounting.nTva"
@update:model-value="(v: string) => accounting.nTva = sanitizeCodeAlnum(v)"
v-model="accounting.nTva"
:mask="CODE_ALNUM_MASK"
:label="t('commercial.clients.form.accounting.nTva')"
:disabled="accountingReadonly"
:required="true"
@@ -336,16 +336,16 @@
:error="ribErrors[index]?.label"
/>
<MalioInputText
:model-value="rib.bic"
@update:model-value="(v: string) => rib.bic = sanitizeCodeAlnum(v)"
v-model="rib.bic"
:mask="CODE_ALNUM_MASK"
:label="t('commercial.clients.form.accounting.ribBic')"
:disabled="accountingReadonly"
:required="isRibRequired"
:error="ribErrors[index]?.bic"
/>
<MalioInputText
:model-value="rib.iban"
@update:model-value="(v: string) => rib.iban = sanitizeCodeAlnum(v)"
v-model="rib.iban"
:mask="CODE_ALNUM_MASK"
:label="t('commercial.clients.form.accounting.ribIban')"
:disabled="accountingReadonly"
:required="isRibRequired"
@@ -439,7 +439,7 @@ import {
} from '~/modules/commercial/utils/forms/clientEdit'
import { clampRevenueAmount } from '~/modules/commercial/utils/forms/amountInput'
import { todayIso } from '~/shared/utils/date'
import { sanitizeCodeAlnum, sanitizeFreeText, sanitizePersonName } from '~/shared/utils/textSanitize'
import { CODE_ALNUM_MASK, FREE_TEXT_MASK, PERSON_NAME_MASK } from '~/shared/utils/textSanitize'
import {
buildClientFormTabKeys,
isAddressValid,
@@ -18,8 +18,8 @@
automatiquement sur l'onglet Information. -->
<div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
:model-value="main.companyName"
@update:model-value="(v: string) => main.companyName = sanitizeFreeText(v)"
v-model="main.companyName"
:mask="FREE_TEXT_MASK"
:label="t('commercial.clients.form.main.companyName')"
:required="true"
:disabled="mainLocked"
@@ -101,8 +101,8 @@
:error="informationErrors.errors.description"
/>
<MalioInputText
:model-value="information.competitors"
@update:model-value="(v: string) => information.competitors = sanitizeFreeText(v)"
v-model="information.competitors"
:mask="FREE_TEXT_MASK"
:label="t('commercial.clients.form.information.competitors')"
:disabled="isValidated('information')"
:error="informationErrors.errors.competitors"
@@ -136,8 +136,8 @@
@update:model-value="onRevenueAmountInput"
/>
<MalioInputText
:model-value="information.directorName"
@update:model-value="(v: string) => information.directorName = sanitizePersonName(v)"
v-model="information.directorName"
:mask="PERSON_NAME_MASK"
:label="t('commercial.clients.form.information.directorName')"
:disabled="isValidated('information')"
:error="informationErrors.errors.directorName"
@@ -252,8 +252,8 @@
:error="accountingErrors.errors.siren"
/>
<MalioInputText
:model-value="accounting.accountNumber"
@update:model-value="(v: string) => accounting.accountNumber = sanitizeCodeAlnum(v)"
v-model="accounting.accountNumber"
:mask="CODE_ALNUM_MASK"
:label="t('commercial.clients.form.accounting.accountNumber')"
:disabled="accountingReadonly"
:required="true"
@@ -270,8 +270,8 @@
@update:model-value="(v: string | number | null) => accounting.tvaModeIri = v === null ? null : String(v)"
/>
<MalioInputText
:model-value="accounting.nTva"
@update:model-value="(v: string) => accounting.nTva = sanitizeCodeAlnum(v)"
v-model="accounting.nTva"
:mask="CODE_ALNUM_MASK"
:label="t('commercial.clients.form.accounting.nTva')"
:disabled="accountingReadonly"
:required="true"
@@ -335,16 +335,16 @@
:error="ribErrors[index]?.label"
/>
<MalioInputText
:model-value="rib.bic"
@update:model-value="(v: string) => rib.bic = sanitizeCodeAlnum(v)"
v-model="rib.bic"
:mask="CODE_ALNUM_MASK"
:label="t('commercial.clients.form.accounting.ribBic')"
:disabled="accountingReadonly"
:required="isRibRequired"
:error="ribErrors[index]?.bic"
/>
<MalioInputText
:model-value="rib.iban"
@update:model-value="(v: string) => rib.iban = sanitizeCodeAlnum(v)"
v-model="rib.iban"
:mask="CODE_ALNUM_MASK"
:label="t('commercial.clients.form.accounting.ribIban')"
:disabled="accountingReadonly"
:required="isRibRequired"
@@ -423,7 +423,7 @@ import {
} from '~/modules/commercial/utils/forms/clientFormRules'
import { clampRevenueAmount } from '~/modules/commercial/utils/forms/amountInput'
import { todayIso } from '~/shared/utils/date'
import { sanitizeCodeAlnum, sanitizeFreeText, sanitizePersonName } from '~/shared/utils/textSanitize'
import { CODE_ALNUM_MASK, FREE_TEXT_MASK, PERSON_NAME_MASK } from '~/shared/utils/textSanitize'
import {
buildAddressPayload,
buildMainPayload,
@@ -23,12 +23,12 @@
roles sans `manage` (ex. Compta). Pas de contact inline (ERP-106). -->
<div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
:model-value="main.companyName"
v-model="main.companyName"
:label="t('commercial.suppliers.form.main.companyName')"
:required="true"
:disabled="businessReadonly"
:error="mainErrors.errors.companyName"
@update:model-value="(v: string) => main.companyName = sanitizeFreeText(v)"
:mask="FREE_TEXT_MASK"
/>
<MalioSelectCheckbox
:model-value="main.categoryIris"
@@ -67,11 +67,11 @@
:error="informationErrors.errors.description"
/>
<MalioInputText
:model-value="information.competitors"
v-model="information.competitors"
:label="t('commercial.suppliers.form.information.competitors')"
:disabled="businessReadonly"
:error="informationErrors.errors.competitors"
@update:model-value="(v: string) => information.competitors = sanitizeFreeText(v)"
:mask="FREE_TEXT_MASK"
/>
<!-- Date de creation jamais dans le futur (ERP-193) : :max plafonne
le calendrier a aujourd'hui et invalide une saisie future. -->
@@ -102,11 +102,11 @@
@update:model-value="onRevenueAmountInput"
/>
<MalioInputText
:model-value="information.directorName"
v-model="information.directorName"
:label="t('commercial.suppliers.form.information.directorName')"
:disabled="businessReadonly"
:error="informationErrors.errors.directorName"
@update:model-value="(v: string) => information.directorName = sanitizePersonName(v)"
:mask="PERSON_NAME_MASK"
/>
<MalioInputAmount
v-model="information.profitAmount"
@@ -223,12 +223,12 @@
:error="accountingErrors.errors.siren"
/>
<MalioInputText
:model-value="accounting.accountNumber"
v-model="accounting.accountNumber"
:label="t('commercial.suppliers.form.accounting.accountNumber')"
:disabled="accountingReadonly"
:required="true"
:error="accountingErrors.errors.accountNumber"
@update:model-value="(v: string) => accounting.accountNumber = sanitizeCodeAlnum(v)"
:mask="CODE_ALNUM_MASK"
/>
<MalioSelect
:model-value="accounting.tvaModeIri"
@@ -241,12 +241,12 @@
@update:model-value="(v: string | number | null) => accounting.tvaModeIri = v === null ? null : String(v)"
/>
<MalioInputText
:model-value="accounting.nTva"
v-model="accounting.nTva"
:label="t('commercial.suppliers.form.accounting.nTva')"
:disabled="accountingReadonly"
:required="true"
:error="accountingErrors.errors.nTva"
@update:model-value="(v: string) => accounting.nTva = sanitizeCodeAlnum(v)"
:mask="CODE_ALNUM_MASK"
/>
<MalioSelect
:model-value="accounting.paymentDelayIri"
@@ -305,20 +305,20 @@
:error="ribErrors[index]?.label"
/>
<MalioInputText
:model-value="rib.bic"
v-model="rib.bic"
:label="t('commercial.suppliers.form.accounting.ribBic')"
:disabled="accountingReadonly"
:required="isRibRequired"
:error="ribErrors[index]?.bic"
@update:model-value="(v: string) => rib.bic = sanitizeCodeAlnum(v)"
:mask="CODE_ALNUM_MASK"
/>
<MalioInputText
:model-value="rib.iban"
v-model="rib.iban"
:label="t('commercial.suppliers.form.accounting.ribIban')"
:disabled="accountingReadonly"
:required="isRibRequired"
:error="ribErrors[index]?.iban"
@update:model-value="(v: string) => rib.iban = sanitizeCodeAlnum(v)"
:mask="CODE_ALNUM_MASK"
/>
</div>
</div>
@@ -428,7 +428,7 @@ import {
} from '~/modules/commercial/types/supplierForm'
import { extractApiErrorMessage } from '~/shared/utils/api'
import { isRowRemovable, removeCollectionRow } from '~/shared/utils/collectionRow'
import { sanitizeCodeAlnum, sanitizeFreeText, sanitizePersonName } from '~/shared/utils/textSanitize'
import { CODE_ALNUM_MASK, FREE_TEXT_MASK, PERSON_NAME_MASK } from '~/shared/utils/textSanitize'
import { readHistoryTab } from '~/shared/utils/historyTab'
// Masques de saisie (la normalisation finale reste serveur).
@@ -18,12 +18,12 @@
automatiquement sur l'onglet Information. Pas de contact inline (ERP-106). -->
<div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
:model-value="main.companyName"
v-model="main.companyName"
:label="t('commercial.suppliers.form.main.companyName')"
:required="true"
:disabled="mainLocked"
:error="mainErrors.errors.companyName"
@update:model-value="(v: string) => main.companyName = sanitizeFreeText(v)"
:mask="FREE_TEXT_MASK"
/>
<MalioSelectCheckbox
:model-value="main.categoryIris"
@@ -61,11 +61,11 @@
:error="informationErrors.errors.description"
/>
<MalioInputText
:model-value="information.competitors"
v-model="information.competitors"
:label="t('commercial.suppliers.form.information.competitors')"
:disabled="isValidated('information')"
:error="informationErrors.errors.competitors"
@update:model-value="(v: string) => information.competitors = sanitizeFreeText(v)"
:mask="FREE_TEXT_MASK"
/>
<!-- Date de creation jamais dans le futur (ERP-193) : :max plafonne
le calendrier a aujourd'hui et invalide une saisie future. -->
@@ -96,11 +96,11 @@
@update:model-value="onRevenueAmountInput"
/>
<MalioInputText
:model-value="information.directorName"
v-model="information.directorName"
:label="t('commercial.suppliers.form.information.directorName')"
:disabled="isValidated('information')"
:error="informationErrors.errors.directorName"
@update:model-value="(v: string) => information.directorName = sanitizePersonName(v)"
:mask="PERSON_NAME_MASK"
/>
<MalioInputAmount
v-model="information.profitAmount"
@@ -217,12 +217,12 @@
:error="accountingErrors.errors.siren"
/>
<MalioInputText
:model-value="accounting.accountNumber"
v-model="accounting.accountNumber"
:label="t('commercial.suppliers.form.accounting.accountNumber')"
:disabled="accountingReadonly"
:required="true"
:error="accountingErrors.errors.accountNumber"
@update:model-value="(v: string) => accounting.accountNumber = sanitizeCodeAlnum(v)"
:mask="CODE_ALNUM_MASK"
/>
<MalioSelect
:model-value="accounting.tvaModeIri"
@@ -235,12 +235,12 @@
@update:model-value="(v: string | number | null) => accounting.tvaModeIri = v === null ? null : String(v)"
/>
<MalioInputText
:model-value="accounting.nTva"
v-model="accounting.nTva"
:label="t('commercial.suppliers.form.accounting.nTva')"
:disabled="accountingReadonly"
:required="true"
:error="accountingErrors.errors.nTva"
@update:model-value="(v: string) => accounting.nTva = sanitizeCodeAlnum(v)"
:mask="CODE_ALNUM_MASK"
/>
<MalioSelect
:model-value="accounting.paymentDelayIri"
@@ -299,20 +299,20 @@
:error="ribErrors[index]?.label"
/>
<MalioInputText
:model-value="rib.bic"
v-model="rib.bic"
:label="t('commercial.suppliers.form.accounting.ribBic')"
:disabled="accountingReadonly"
:required="isRibRequired"
:error="ribErrors[index]?.bic"
@update:model-value="(v: string) => rib.bic = sanitizeCodeAlnum(v)"
:mask="CODE_ALNUM_MASK"
/>
<MalioInputText
:model-value="rib.iban"
v-model="rib.iban"
:label="t('commercial.suppliers.form.accounting.ribIban')"
:disabled="accountingReadonly"
:required="isRibRequired"
:error="ribErrors[index]?.iban"
@update:model-value="(v: string) => rib.iban = sanitizeCodeAlnum(v)"
:mask="CODE_ALNUM_MASK"
/>
</div>
</div>
@@ -401,7 +401,7 @@ import {
} from '~/modules/commercial/types/supplierForm'
import { extractApiErrorMessage } from '~/shared/utils/api'
import { isRowRemovable } from '~/shared/utils/collectionRow'
import { sanitizeCodeAlnum, sanitizeFreeText, sanitizePersonName } from '~/shared/utils/textSanitize'
import { CODE_ALNUM_MASK, FREE_TEXT_MASK, PERSON_NAME_MASK } from '~/shared/utils/textSanitize'
// Masques de saisie (la normalisation finale reste serveur).
const SIREN_MASK = '#########'