diff --git a/frontend/modules/commercial/composables/__tests__/useClientFormErrors.spec.ts b/frontend/modules/commercial/composables/__tests__/useClientFormErrors.spec.ts index c3f8470..ff1bed9 100644 --- a/frontend/modules/commercial/composables/__tests__/useClientFormErrors.spec.ts +++ b/frontend/modules/commercial/composables/__tests__/useClientFormErrors.spec.ts @@ -52,3 +52,71 @@ describe('useClientFormErrors', () => { expect(f.addressErrors.value[0]).toBeUndefined() }) }) + +// Construit une erreur facon useApi : 422 avec violations Hydra. +function http422(path: string, message: string) { + return { response: { status: 422, _data: { violations: [{ propertyPath: path, message }] } } } +} + +/** + * `submitRows` factorise la soumission d'une collection de blocs (contacts / + * adresses / RIB) : on tente TOUS les blocs et on collecte les erreurs par index + * sans stopper au premier echec (ERP-110 / ERP-101). + */ +describe('useClientFormErrors.submitRows', () => { + it('tente TOUS les blocs et mappe les erreurs par index, sans stopper au premier echec', async () => { + const { contactErrors, submitRows } = useClientFormErrors() + const seen: number[] = [] + const onUnmapped = vi.fn() + + const saveRow = async (_row: unknown, index: number) => { + seen.push(index) + if (index === 1) throw http422('email', 'Email invalide') + } + + const hasError = await submitRows( + [{ a: 0 }, { a: 1 }, { a: 2 }], + contactErrors, + saveRow, + onUnmapped, + ) + + expect(seen).toEqual([0, 1, 2]) // tous les blocs tentes + expect(hasError).toBe(true) + expect(contactErrors.value[1]).toEqual({ email: 'Email invalide' }) + expect(contactErrors.value[0]).toBeUndefined() + expect(onUnmapped).not.toHaveBeenCalled() // 422 mappee, pas de fallback + }) + + it('delegue le fallback onUnmappedError pour une erreur non mappable et marque hasError', async () => { + const { ribErrors, submitRows } = useClientFormErrors() + const onUnmapped = vi.fn() + + const hasError = await submitRows( + [{ a: 0 }], + ribErrors, + async () => { throw { response: { status: 500, _data: {} } } }, + onUnmapped, + ) + + expect(hasError).toBe(true) + expect(onUnmapped).toHaveBeenCalledTimes(1) + expect(ribErrors.value[0]).toBeUndefined() + }) + + it('saute les lignes filtrees par shouldSkip et renvoie false si tout passe', async () => { + const { contactErrors, submitRows } = useClientFormErrors() + const saved: number[] = [] + + const hasError = await submitRows( + [{ skip: true }, { skip: false }], + contactErrors, + async (_row, index) => { saved.push(index) }, + vi.fn(), + (row: { skip: boolean }) => row.skip, + ) + + expect(saved).toEqual([1]) + expect(hasError).toBe(false) + }) +}) diff --git a/frontend/modules/commercial/composables/useClientFormErrors.ts b/frontend/modules/commercial/composables/useClientFormErrors.ts index dcb10d6..86551e8 100644 --- a/frontend/modules/commercial/composables/useClientFormErrors.ts +++ b/frontend/modules/commercial/composables/useClientFormErrors.ts @@ -43,6 +43,44 @@ export function useClientFormErrors() { 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, @@ -51,5 +89,6 @@ export function useClientFormErrors() { addressErrors, ribErrors, mapRowError, + submitRows, } }