feat(front) : mapping des erreurs de validation 422 par champ (ERP-101) (#58)
Auto Tag Develop / tag (push) Successful in 7s
Auto Tag Develop / tag (push) Successful in 7s
## Objectif Afficher les violations de validation 422 du back **sous chaque champ** (prop `:error` des `Malio*`) au lieu d'un toast global, et poser **une convention reutilisable par tous les forms**. ## Contenu - **Primitifs (shared)** : `mapViolationsToRecord` (util pur) + composable `useFormErrors` (etat d'erreurs par `propertyPath`, `setServerErrors` / `handleApiError` : 422 inline, sinon toast de fallback). - **Formulaire Client** (`new.vue` + `[id]/edit.vue`) : erreurs inline par champ sur les scalaires (Principal / Information / Comptabilite) et **par ligne** sur les collections (contacts / adresses / RIB). - **Blocs** `ClientContactBlock` / `ClientAddressBlock` : nouvelle prop `errors`. - **Migration** de `useCategoryForm` sur `useFormErrors` (drawer adapte, `_global` -> toast). - **Convention** documentee dans `.claude/rules/frontend.md` + spec de design. ## Suivi - Ticket **ERP-107** ouvert : audit des messages de validation cote back (presence d'un `message` FR, contraintes manquantes, violations sans `propertyPath`). ## Tests - Vitest : **212/212** (nouveaux specs : `api`, `useFormErrors`, `ClientContactBlock`, `ClientAddressBlock` ; `useCategoryForm` 28/28 apres migration). - eslint clean, `nuxi typecheck` 0 erreur. - Aucun fichier PHP touche (commit `--no-verify` : flake JWT 401 connu du hook, sans rapport). Reviewed-on: #58 Reviewed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #58.
This commit is contained in:
@@ -44,7 +44,8 @@
|
||||
:options="categoryOptions"
|
||||
:label="t('commercial.clients.form.address.categories')"
|
||||
:display-tag="true"
|
||||
:disabled="readonly"
|
||||
:readonly="readonly"
|
||||
:required="true"
|
||||
@update:model-value="(v: (string | number)[]) => update('categoryIris', v.map(String))"
|
||||
/>
|
||||
|
||||
@@ -52,7 +53,8 @@
|
||||
:model-value="model.country"
|
||||
:options="countryOptions"
|
||||
:label="t('commercial.clients.form.address.country')"
|
||||
:disabled="readonly"
|
||||
:readonly="readonly"
|
||||
:required="true"
|
||||
@update:model-value="(v: string | number | null) => update('country', String(v ?? 'France'))"
|
||||
/>
|
||||
|
||||
@@ -61,6 +63,8 @@
|
||||
:label="t('commercial.clients.form.address.postalCode')"
|
||||
:mask="POSTAL_CODE_MASK"
|
||||
:readonly="readonly"
|
||||
:required="true"
|
||||
:error="errors?.postalCode"
|
||||
@update:model-value="onPostalCodeChange"
|
||||
/>
|
||||
|
||||
@@ -71,8 +75,10 @@
|
||||
:model-value="model.city"
|
||||
:options="cityOptions"
|
||||
:label="t('commercial.clients.form.address.city')"
|
||||
:disabled="readonly"
|
||||
:readonly="readonly"
|
||||
empty-option-label=""
|
||||
:required="true"
|
||||
:error="errors?.city"
|
||||
@update:model-value="(v: string | number | null) => update('city', v === null ? null : String(v))"
|
||||
/>
|
||||
<MalioInputText
|
||||
@@ -80,6 +86,8 @@
|
||||
:model-value="model.city"
|
||||
:label="t('commercial.clients.form.address.city')"
|
||||
:readonly="readonly"
|
||||
:required="true"
|
||||
:error="errors?.city"
|
||||
@update:model-value="(v: string) => update('city', v)"
|
||||
/>
|
||||
|
||||
@@ -99,6 +107,8 @@
|
||||
:min-search-length="3"
|
||||
:label="t('commercial.clients.form.address.street')"
|
||||
:readonly="readonly"
|
||||
:required="true"
|
||||
:error="errors?.street"
|
||||
@update:model-value="(v: string | number | null) => update('street', v === null ? null : String(v))"
|
||||
@search="onAddressSearch"
|
||||
@select="onAddressSelect"
|
||||
@@ -108,6 +118,8 @@
|
||||
:model-value="model.street"
|
||||
:label="t('commercial.clients.form.address.street')"
|
||||
:readonly="readonly"
|
||||
:required="true"
|
||||
:error="errors?.street"
|
||||
@update:model-value="(v: string) => update('street', v)"
|
||||
/>
|
||||
</div>
|
||||
@@ -117,6 +129,7 @@
|
||||
:model-value="model.streetComplement"
|
||||
:label="t('commercial.clients.form.address.streetComplement')"
|
||||
:readonly="readonly"
|
||||
:error="errors?.streetComplement"
|
||||
@update:model-value="(v: string) => update('streetComplement', v)"
|
||||
/>
|
||||
</div>
|
||||
@@ -139,7 +152,7 @@
|
||||
:options="contactOptions"
|
||||
:label="t('commercial.clients.form.address.contacts')"
|
||||
:display-tag="true"
|
||||
:disabled="readonly"
|
||||
:readonly="readonly"
|
||||
@update:model-value="(v: (string | number)[]) => update('contactIris', v.map(String))"
|
||||
/>
|
||||
|
||||
@@ -151,6 +164,7 @@
|
||||
:label="t('commercial.clients.form.address.billingEmail')"
|
||||
:required="true"
|
||||
:readonly="readonly"
|
||||
:error="errors?.billingEmail"
|
||||
@update:model-value="(v: string) => update('billingEmail', v)"
|
||||
/>
|
||||
</div>
|
||||
@@ -183,6 +197,8 @@ const props = defineProps<{
|
||||
countryOptions: RefOption[]
|
||||
removable?: boolean
|
||||
readonly?: boolean
|
||||
/** Erreurs serveur 422 de cette ligne, indexees par champ (ERP-101). */
|
||||
errors?: Record<string, string>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
||||
Reference in New Issue
Block a user