feat(commercial) : valide tous les blocs contacts/adresses/RIB et affiche les erreurs par bloc (ERP-110)
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Has been cancelled
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Has been cancelled

This commit is contained in:
Matthieu
2026-06-04 11:42:08 +02:00
parent c0645233ef
commit 41d391eebf
2 changed files with 94 additions and 112 deletions
@@ -628,7 +628,7 @@ const {
contactErrors,
addressErrors,
ribErrors,
mapRowError,
submitRows,
} = useClientFormErrors()
// ── Bloc principal ───────────────────────────────────────────────────────────
@@ -742,11 +742,13 @@ async function submitContacts(): Promise<void> {
}
removedContactIds.value = []
for (let index = 0; index < contacts.value.length; index++) {
const contact = contacts.value[index]
if (!isContactNamed(contact)) continue
const body = buildContactPayload(contact)
try {
// On tente TOUS les blocs (collecte des erreurs par index, ERP-110) ; les
// blocs vides (ni nom ni prenom) sont ignores.
const hasError = await submitRows(
contacts.value,
contactErrors,
async (contact) => {
const body = buildContactPayload(contact)
if (contact.id === null) {
const created = await api.post<{ '@id'?: string, id: number }>(
`/clients/${clientId}/contacts`,
@@ -759,15 +761,12 @@ async function submitContacts(): Promise<void> {
else {
await api.patch(`/client_contacts/${contact.id}`, body, { toast: false })
}
}
catch (error) {
// 422 → erreurs inline sous les champs de CETTE ligne ; on stoppe.
if (!mapRowError(error, contactErrors, index)) {
showError(error)
}
return
}
}
},
error => showError(error),
contact => !isContactNamed(contact),
)
// Tant qu'un bloc reste en erreur : pas de toast succes.
if (hasError) return
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
}
catch (e) {
@@ -824,10 +823,12 @@ async function submitAddresses(): Promise<void> {
}
removedAddressIds.value = []
for (let index = 0; index < addresses.value.length; index++) {
const address = addresses.value[index]
const body = buildAddressPayload(address, isBillingEmailRequired(address))
try {
// On tente TOUS les blocs d'adresse (collecte des erreurs par index, ERP-110).
const hasError = await submitRows(
addresses.value,
addressErrors,
async (address) => {
const body = buildAddressPayload(address, isBillingEmailRequired(address))
if (address.id === null) {
const created = await api.post<{ id: number }>(
`/clients/${clientId}/addresses`,
@@ -839,14 +840,10 @@ async function submitAddresses(): Promise<void> {
else {
await api.patch(`/client_addresses/${address.id}`, body, { toast: false })
}
}
catch (error) {
if (!mapRowError(error, addressErrors, index)) {
showError(error)
}
return
}
}
},
error => showError(error),
)
if (hasError) return
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
}
catch (e) {
@@ -905,7 +902,6 @@ async function submitAccounting(): Promise<void> {
if (accountingReadonly.value || !canValidateAccounting.value || tabSubmitting.value) return
tabSubmitting.value = true
accountingErrors.clearErrors()
ribErrors.value = []
try {
// 1) PATCH des scalaires comptables (erreurs inline sur leurs champs).
try {
@@ -921,12 +917,13 @@ async function submitAccounting(): Promise<void> {
}
removedRibIds.value = []
// 2) POST/PATCH des RIB (erreurs inline par ligne).
for (let index = 0; index < ribs.value.length; index++) {
const rib = ribs.value[index]
if (!ribIsComplete(rib)) continue
const body = buildRibPayload(rib)
try {
// 2) POST/PATCH des RIB (erreurs inline par ligne, tous les blocs tentes —
// les blocs RIB incomplets sont ignores).
const ribHasError = await submitRows(
ribs.value,
ribErrors,
async (rib) => {
const body = buildRibPayload(rib)
if (rib.id === null) {
const created = await api.post<{ id: number }>(
`/clients/${clientId}/ribs`,
@@ -938,14 +935,11 @@ async function submitAccounting(): Promise<void> {
else {
await api.patch(`/client_ribs/${rib.id}`, body, { toast: false })
}
}
catch (error) {
if (!mapRowError(error, ribErrors, index)) {
showError(error)
}
return
}
}
},
error => showError(error),
rib => !ribIsComplete(rib),
)
if (ribHasError) return
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
}
catch (e) {