feat : bloque les caractères spéciaux dans les champs texte des 4 répertoires (ERP-193)

This commit is contained in:
2026-06-19 09:46:23 +02:00
parent 403dc4a870
commit 07f5a95a6b
32 changed files with 537 additions and 58 deletions
@@ -131,6 +131,7 @@
import { useAddressAutocomplete, type AddressSuggestion } from '~/shared/composables/useAddressAutocomplete'
import type { RefOption } from '~/modules/technique/composables/useProviderReferentials'
import type { ProviderAddressFormDraft } from '~/modules/technique/types/providerForm'
import { sanitizeAddress } from '~/shared/utils/textSanitize'
// Masque code postal FR : 5 chiffres.
const POSTAL_CODE_MASK = '#####'
@@ -193,9 +194,20 @@ const addressLoading = ref(false)
// Conserve les suggestions d'adresse pour retrouver ville/CP au moment du select.
let lastAddressSuggestions: AddressSuggestion[] = []
/** Emet un nouveau brouillon avec le champ modifie (immutabilite). */
// Filtres de saisie par champ (ERP-193) : voie / complement / ville = profil adresse.
const FIELD_SANITIZERS: Partial<Record<keyof ProviderAddressFormDraft, (v: string) => string>> = {
street: sanitizeAddress,
streetComplement: sanitizeAddress,
city: sanitizeAddress,
}
/** Emet un nouveau brouillon avec le champ modifie (immutabilite), sanitise si besoin. */
function update<K extends keyof ProviderAddressFormDraft>(field: K, value: ProviderAddressFormDraft[K]): void {
emit('update:modelValue', { ...props.modelValue, [field]: value })
const sanitizer = FIELD_SANITIZERS[field]
const next = (sanitizer && typeof value === 'string')
? (sanitizer(value) as ProviderAddressFormDraft[K])
: value
emit('update:modelValue', { ...props.modelValue, [field]: next })
}
/** Previent le parent (toast unique) que l'autocompletion est indisponible. */
@@ -71,6 +71,7 @@
<script setup lang="ts">
import type { ProviderContactFormDraft } from '~/modules/technique/types/providerForm'
import { sanitizeEmail, sanitizeFreeText, sanitizePersonName } from '~/shared/utils/textSanitize'
// Masque telephone FR : 5 groupes de 2 chiffres (la normalisation finale reste serveur).
const PHONE_MASK = '## ## ## ## ##'
@@ -96,9 +97,22 @@ const { t } = useI18n()
// Alias local pour la lisibilite du template.
const model = computed(() => props.modelValue)
/** Emet un nouveau brouillon avec le champ modifie (immutabilite). */
// Filtres de saisie par champ (ERP-193) : on retire les caracteres parasites a la
// frappe. Noms = profil personne, fonction = texte libre, email = profil email.
const FIELD_SANITIZERS: Partial<Record<keyof ProviderContactFormDraft, (v: string) => string>> = {
lastName: sanitizePersonName,
firstName: sanitizePersonName,
jobTitle: sanitizeFreeText,
email: sanitizeEmail,
}
/** Emet un nouveau brouillon avec le champ modifie (immutabilite), sanitise si besoin. */
function update<K extends keyof ProviderContactFormDraft>(field: K, value: ProviderContactFormDraft[K]): void {
emit('update:modelValue', { ...props.modelValue, [field]: value })
const sanitizer = FIELD_SANITIZERS[field]
const next = (sanitizer && typeof value === 'string')
? (sanitizer(value) as ProviderContactFormDraft[K])
: value
emit('update:modelValue', { ...props.modelValue, [field]: next })
}
/** Revele le 2e numero (max 1 secondaire, le « + » disparait). */
@@ -20,11 +20,12 @@
<!-- Bloc principal (pre-rempli, editable si `manage`) -->
<div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
v-model="main.companyName"
:model-value="main.companyName"
:label="t('technique.providers.form.main.companyName')"
:required="true"
:readonly="businessReadonly"
:error="mainErrors.errors.companyName"
@update:model-value="(v: string) => main.companyName = sanitizeFreeText(v)"
/>
<MalioSelectCheckbox
:model-value="main.categoryIris"
@@ -146,11 +147,12 @@
:error="accountingErrors.errors.siren"
/>
<MalioInputText
v-model="accounting.accountNumber"
:model-value="accounting.accountNumber"
:label="t('technique.providers.form.accounting.accountNumber')"
:readonly="accountingReadonly"
:required="true"
:error="accountingErrors.errors.accountNumber"
@update:model-value="(v: string) => accounting.accountNumber = sanitizeCodeAlnum(v)"
/>
<MalioSelect
:model-value="accounting.tvaModeIri"
@@ -163,11 +165,12 @@
@update:model-value="(v: string | number | null) => accounting.tvaModeIri = v === null ? null : String(v)"
/>
<MalioInputText
v-model="accounting.nTva"
:model-value="accounting.nTva"
:label="t('technique.providers.form.accounting.nTva')"
:readonly="accountingReadonly"
:required="true"
:error="accountingErrors.errors.nTva"
@update:model-value="(v: string) => accounting.nTva = sanitizeCodeAlnum(v)"
/>
<MalioSelect
:model-value="accounting.paymentDelayIri"
@@ -226,18 +229,20 @@
:error="ribErrors[index]?.label"
/>
<MalioInputText
v-model="rib.bic"
:model-value="rib.bic"
:label="t('technique.providers.form.accounting.ribBic')"
:readonly="accountingReadonly"
:required="true"
:error="ribErrors[index]?.bic"
@update:model-value="(v: string) => rib.bic = sanitizeCodeAlnum(v)"
/>
<MalioInputText
v-model="rib.iban"
:model-value="rib.iban"
:label="t('technique.providers.form.accounting.ribIban')"
:readonly="accountingReadonly"
:required="true"
:error="ribErrors[index]?.iban"
@update:model-value="(v: string) => rib.iban = sanitizeCodeAlnum(v)"
/>
</div>
</div>
@@ -313,6 +318,7 @@ import {
} from '~/modules/technique/types/providerForm'
import { extractApiErrorMessage } from '~/shared/utils/api'
import { isRowRemovable } from '~/shared/utils/collectionRow'
import { sanitizeCodeAlnum, sanitizeFreeText } from '~/shared/utils/textSanitize'
// Masque SIREN : 9 chiffres (la normalisation finale reste serveur).
const SIREN_MASK = '#########'
@@ -19,11 +19,12 @@
Selecteur de site present ici (RG-3.03, relation directe). -->
<div class="mt-[48px] grid grid-cols-3 xl:grid-cols-4 gap-x-[44px] gap-y-4">
<MalioInputText
v-model="main.companyName"
:model-value="main.companyName"
:label="t('technique.providers.form.main.companyName')"
:required="true"
:readonly="mainLocked"
:error="mainErrors.errors.companyName"
@update:model-value="(v: string) => main.companyName = sanitizeFreeText(v)"
/>
<MalioSelectCheckbox
:model-value="main.categoryIris"
@@ -145,11 +146,12 @@
:error="accountingErrors.errors.siren"
/>
<MalioInputText
v-model="accounting.accountNumber"
:model-value="accounting.accountNumber"
:label="t('technique.providers.form.accounting.accountNumber')"
:readonly="accountingReadonly"
:required="true"
:error="accountingErrors.errors.accountNumber"
@update:model-value="(v: string) => accounting.accountNumber = sanitizeCodeAlnum(v)"
/>
<MalioSelect
:model-value="accounting.tvaModeIri"
@@ -162,11 +164,12 @@
@update:model-value="(v: string | number | null) => accounting.tvaModeIri = v === null ? null : String(v)"
/>
<MalioInputText
v-model="accounting.nTva"
:model-value="accounting.nTva"
:label="t('technique.providers.form.accounting.nTva')"
:readonly="accountingReadonly"
:required="true"
:error="accountingErrors.errors.nTva"
@update:model-value="(v: string) => accounting.nTva = sanitizeCodeAlnum(v)"
/>
<MalioSelect
:model-value="accounting.paymentDelayIri"
@@ -226,18 +229,20 @@
:error="ribErrors[index]?.label"
/>
<MalioInputText
v-model="rib.bic"
:model-value="rib.bic"
:label="t('technique.providers.form.accounting.ribBic')"
:readonly="accountingReadonly"
:required="true"
:error="ribErrors[index]?.bic"
@update:model-value="(v: string) => rib.bic = sanitizeCodeAlnum(v)"
/>
<MalioInputText
v-model="rib.iban"
:model-value="rib.iban"
:label="t('technique.providers.form.accounting.ribIban')"
:readonly="accountingReadonly"
:required="true"
:error="ribErrors[index]?.iban"
@update:model-value="(v: string) => rib.iban = sanitizeCodeAlnum(v)"
/>
</div>
</div>
@@ -297,6 +302,7 @@ import {
} from '~/modules/technique/utils/forms/providerAccounting'
import { extractApiErrorMessage } from '~/shared/utils/api'
import { isRowRemovable } from '~/shared/utils/collectionRow'
import { sanitizeCodeAlnum, sanitizeFreeText } from '~/shared/utils/textSanitize'
// Masque SIREN : 9 chiffres (la normalisation finale reste serveur).
const SIREN_MASK = '#########'