d6790dd37d
Auto Tag Develop / tag (push) Successful in 7s
ERP-94 (etape front 7/7 du M2). **Stack sur #97** (base = `feature/ERP-97-suppliers-i18n-sidebar`, elle-meme sur #93) pour un diff isole. A recibler sur `develop` une fois #93 (MR #81) et #97 (MR #82) mergees. Page « Ajouter un fournisseur » — **replique a l'identique le fonctionnement de l'ecran Client** (workflow inline par onglets, blocs reutilisables, validation 422 inline ERP-101), avec les specificites M2. ## Architecture (miroir Client) - Workflow par onglets **inline dans `suppliers/new.vue`** (comme `clients/new.vue` — il n'existe pas de `useClientForm` monolithique). Helpers paralleles : `useSupplierReferentials`, `useSupplierFormErrors`, `supplierFormRules`, `supplierEdit` (payloads), `types/supplierForm`. - Blocs `SupplierContactBlock` / `SupplierAddressBlock` (miroir des blocs Client). - POST `/suppliers` puis PATCH partiels par onglet (mode strict, groupes de serialisation). Sous-ressources : `/suppliers/{id}/contacts|addresses|ribs`. - Validation ERP-101 : 422 `violations[].propertyPath` mappees inline par champ (`useFormErrors` / `mapViolationsToRecord`), `{ toast: false }`, bouton Valider toujours actif. ## Specificites M2 (vs M1) - Formulaire principal **sans contact inline** (ERP-106) : Entreprise + Categorie (type FOURNISSEUR, `?typeCode=FOURNISSEUR`). - Adresse : **radio exclusif** Prospect/Depart/Rendu (`addressType` enum, RG-2.09), champs **Bennes** (stepper) + **Prestation de triage**, **pas d'email de facturation**. - Information : champ **Volume previsionnel** (8e champ). - Compta (Admin+Compta) : banque si VIREMENT (RG-2.07), RIB si LCR (RG-2.08) ; RIB sous-ressource gardee par `accounting.manage`. ## Tests (mirroir strategie Client) - `make nuxt-test` : 338 passed (specs ajoutees : supplierFormRules, supplierEdit, useSupplierReferentials, SupplierContactBlock, SupplierAddressBlock). - ESLint propre ; `nuxi typecheck` (lance en container) : **0 erreur**. - Golden path navigateur valide end-to-end : POST /suppliers OK, companyName normalise UPPERCASE (RG-2.12), gating des onglets (Information actif, Contacts deverrouille). ## Note de revue ~30 `WARN Duplicated imports` au typecheck : les helpers Supplier exportent les memes noms generiques que leurs equivalents Client (`buildMainPayload`, `omitEmptyRequired`, `RefOption`...), tous deux auto-importes par Nuxt. **Sans impact runtime** : tous les consommateurs utilisent des imports explicites (qui priment). Consequence directe du miroir 1:1 ; une factorisation des generiques dans `shared/` pourrait etre un suivi. Reviewed-on: #83 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
89 lines
3.2 KiB
TypeScript
89 lines
3.2 KiB
TypeScript
/**
|
|
* Composable d'erreurs partage des ecrans fournisseur (creation + edition, M2
|
|
* Commercial). Miroir de `useClientFormErrors` (M1) :
|
|
* - 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`.
|
|
*/
|
|
import { ref, type Ref } from 'vue'
|
|
import { mapViolationsToRecord } from '~/shared/utils/api'
|
|
|
|
export function useSupplierFormErrors() {
|
|
const mainErrors = useFormErrors()
|
|
const informationErrors = useFormErrors()
|
|
const accountingErrors = useFormErrors()
|
|
const contactErrors = ref<Record<string, string>[]>([])
|
|
const addressErrors = ref<Record<string, string>[]>([])
|
|
const ribErrors = ref<Record<string, string>[]>([])
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
function mapRowError(
|
|
error: unknown,
|
|
target: Ref<Record<string, string>[]>,
|
|
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.
|
|
* Retourne true si au moins un bloc a echoue.
|
|
*/
|
|
async function submitRows<T>(
|
|
rows: T[],
|
|
target: Ref<Record<string, string>[]>,
|
|
saveRow: (row: T, index: number) => Promise<void>,
|
|
onUnmappedError: (error: unknown, index: number) => void,
|
|
shouldSkip?: (row: T, index: number) => boolean,
|
|
): Promise<boolean> {
|
|
target.value = []
|
|
let hasError = false
|
|
for (let index = 0; index < rows.length; index++) {
|
|
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,
|
|
}
|
|
}
|