/** * Composable d'erreurs partage des ecrans client (creation + edition, M1 * Commercial). Factorise le cablage identique entre `clients/new.vue` et * `clients/[id]/edit.vue` (suggestion de revue ERP-101) : * - un `useFormErrors` par groupe scalaire (Principal / Information / * Comptabilite) : violations 422 affichees inline sous chaque champ ; * - un tableau d'erreurs PAR LIGNE pour chaque collection (contacts / * adresses / RIB), aligne sur l'index du `v-for`. * * `mapRowError` ne toaste PAS lui-meme : il retourne un booleen (true = mappe * inline). Chaque page conserve ainsi son propre fallback dans le `catch` * (toast generique en creation, `showError` en edition) sans imposer un * comportement commun. */ import { ref, type Ref } from 'vue' import { mapViolationsToRecord } from '~/shared/utils/api' export function useClientFormErrors() { const mainErrors = useFormErrors() const informationErrors = useFormErrors() const accountingErrors = useFormErrors() const contactErrors = ref[]>([]) const addressErrors = ref[]>([]) const ribErrors = ref[]>([]) /** * Mappe l'erreur d'une ligne de collection sur le tableau cible (par index). * 422 avec violations exploitables → erreurs inline sous les champs de la * ligne + retourne true. Sinon → ne touche pas la cible et retourne false * (le caller decide du fallback toast). */ function mapRowError( error: unknown, target: Ref[]>, index: number, ): boolean { const response = (error as { response?: { status?: number, _data?: unknown } })?.response const mapped = response?.status === 422 ? mapViolationsToRecord(response._data) : {} if (Object.keys(mapped).length > 0) { target.value[index] = mapped return true } return false } /** * Soumet TOUS les blocs d'une collection (contacts / adresses / RIB) en * collectant les erreurs par index : on n'arrete PAS au premier bloc en echec * (decision ERP-110 / ERP-101). Reinitialise le tableau d'erreurs cible, tente * chaque ligne via `saveRow`, mappe les 422 inline (mapRowError) ou delegue le * fallback a `onUnmappedError`. `shouldSkip` permet d'ignorer les blocs vides * (non remplis). Retourne true si au moins un bloc a echoue (le caller ne valide * alors pas l'onglet et n'affiche pas de toast succes). */ async function submitRows( rows: T[], target: Ref[]>, saveRow: (row: T, index: number) => Promise, onUnmappedError: (error: unknown, index: number) => void, shouldSkip?: (row: T, index: number) => boolean, ): Promise { target.value = [] let hasError = false for (let index = 0; index < rows.length; index++) { // L'index reste borne par rows.length : la ligne existe forcement. const row = rows[index] as T if (shouldSkip?.(row, index)) { continue } try { await saveRow(row, index) } catch (error) { if (!mapRowError(error, target, index)) { onUnmappedError(error, index) } hasError = true } } return hasError } return { mainErrors, informationErrors, accountingErrors, contactErrors, addressErrors, ribErrors, mapRowError, submitRows, } }