fix(commercial) : creer les RIB avant le PATCH scalaires compta (LCR)

L'onglet Comptabilite envoyait le PATCH des scalaires (paymentType=LCR) AVANT
le POST des RIB. Le back valide RG-1.13 (LCR => au moins un RIB persiste) sur ce
PATCH en lisant les RIB en base : vides a ce stade -> 422 « Au moins un RIB est
obligatoire pour le type de reglement LCR », et le return empechait la creation
des RIB. Premier passage en LCR impossible.

Ordre inverse : POST/PATCH des RIB d'abord, puis PATCH des scalaires. Sur l'ecran
edition, ordre universel sur CREATE/UPDATE RIB -> PATCH scalaires -> DELETE RIB
retires (les suppressions restent apres le PATCH : le guard back n'autorise la
suppression du dernier RIB qu'une fois quitte LCR). Corrige au passage un 409
latent sur le swap du dernier RIB en LCR.
This commit is contained in:
2026-06-09 10:26:41 +02:00
parent d4a5df50a7
commit c73111121f
2 changed files with 51 additions and 48 deletions
@@ -956,35 +956,21 @@ function askRemoveRib(index: number): void {
} }
/** /**
* Valide l'onglet Comptabilite : PATCH des scalaires (groupe client:write:accounting, * Valide l'onglet Comptabilite : POST/PATCH des RIB sur la sous-ressource PUIS
* exige accounting.manage cote back) PUIS DELETE/POST/PATCH des RIB sur la * PATCH des scalaires (groupe client:write:accounting, exige accounting.manage cote
* sous-ressource. Aucun champ main/information dans le payload (mode strict * back) PUIS DELETE des RIB retires. Les RIB crees d'abord : le back valide RG-1.13
* RG-1.28 : sinon 403 sur tout le payload). * (LCR => au moins un RIB persiste) sur le PATCH scalaires ; les suppressions en
* dernier (le guard back n'autorise la suppression du dernier RIB qu'une fois quitte
* LCR). Aucun champ main/information dans le payload (mode strict RG-1.28 : sinon
* 403 sur tout le payload).
*/ */
async function submitAccounting(): Promise<void> { async function submitAccounting(): Promise<void> {
if (accountingReadonly.value || !canValidateAccounting.value || tabSubmitting.value) return if (accountingReadonly.value || !canValidateAccounting.value || tabSubmitting.value) return
tabSubmitting.value = true tabSubmitting.value = true
accountingErrors.clearErrors() accountingErrors.clearErrors()
// Reset des erreurs RIB des le debut : l'etape 1 (PATCH scalaires) peut
// echouer et `return` avant submitRows (qui porte sinon le reset), laissant
// des erreurs de RIB obsoletes affichees sous les blocs.
ribErrors.value = []
try { try {
// 1) PATCH des scalaires comptables (erreurs inline sur leurs champs). // 1) POST/PATCH des RIB d'abord (erreurs inline par ligne, tous les blocs
try { // tentes). Le back exige >=1 RIB persiste pour valider une LCR a l'etape 2.
await api.patch(`/clients/${clientId}`, buildAccountingPayload(accounting, isBankRequired.value), { toast: false })
}
catch (error) {
accountingErrors.handleApiError(error, { fallbackMessage: t('commercial.clients.toast.error') })
return
}
for (const id of removedRibIds.value) {
await api.delete(`/client_ribs/${id}`, {}, { toast: false })
}
removedRibIds.value = []
// 2) POST/PATCH des RIB (erreurs inline par ligne, tous les blocs tentes).
// Seuls les blocs RIB TOTALEMENT vides sont ignores : un RIB partiel (ex. // Seuls les blocs RIB TOTALEMENT vides sont ignores : un RIB partiel (ex.
// IBAN seul) est soumis -> 422 NotBlank (label / bic / iban) inline. // IBAN seul) est soumis -> 422 NotBlank (label / bic / iban) inline.
const ribHasError = await submitRows( const ribHasError = await submitRows(
@@ -1011,6 +997,23 @@ async function submitAccounting(): Promise<void> {
rib => rib.id === null && isRibBlank(rib), rib => rib.id === null && isRibBlank(rib),
) )
if (ribHasError) return if (ribHasError) return
// 2) PATCH des scalaires comptables (erreurs inline sur leurs champs).
try {
await api.patch(`/clients/${clientId}`, buildAccountingPayload(accounting, isBankRequired.value), { toast: false })
}
catch (error) {
accountingErrors.handleApiError(error, { fallbackMessage: t('commercial.clients.toast.error') })
return
}
// 3) DELETE des RIB retires : APRES le PATCH scalaires (si on quitte LCR, le
// guard back n'autorise la suppression du dernier RIB qu'une fois le type change).
for (const id of removedRibIds.value) {
await api.delete(`/client_ribs/${id}`, {}, { toast: false })
}
removedRibIds.value = []
toast.success({ title: t('commercial.clients.toast.updateSuccess') }) toast.success({ title: t('commercial.clients.toast.updateSuccess') })
} }
catch (e) { catch (e) {
@@ -939,37 +939,20 @@ function askRemoveRib(index: number): void {
} }
/** /**
* Valide l'onglet Comptabilite : PATCH des scalaires (groupe client:write:accounting) * Valide l'onglet Comptabilite : POST/PATCH des RIB sur /clients/{id}/ribs PUIS
* PUIS POST des RIB sur /clients/{id}/ribs. Deux appels distincts (mode strict * PATCH des scalaires (groupe client:write:accounting). Les RIB d'abord : le back
* RG-1.28 : il n'existe pas d'endpoint /accounting, cf. recon back). * valide RG-1.13 (LCR => au moins un RIB persiste) sur le PATCH scalaires, les RIB
* doivent donc exister en base AVANT (sinon 422 « Au moins un RIB est obligatoire
* pour le type de reglement LCR »). Deux appels distincts (mode strict RG-1.28 :
* il n'existe pas d'endpoint /accounting, cf. recon back).
*/ */
async function submitAccounting(): Promise<void> { async function submitAccounting(): Promise<void> {
if (clientId.value === null || !canValidateAccounting.value || tabSubmitting.value) return if (clientId.value === null || !canValidateAccounting.value || tabSubmitting.value) return
tabSubmitting.value = true tabSubmitting.value = true
accountingErrors.clearErrors() accountingErrors.clearErrors()
// Reset des erreurs RIB des le debut : l'etape 1 (PATCH scalaires) peut
// echouer et `return` avant submitRows (qui porte sinon le reset), laissant
// des erreurs de RIB obsoletes affichees sous les blocs.
ribErrors.value = []
try { try {
// 1) PATCH des scalaires comptables (erreurs inline sur leurs champs). // 1) POST/PATCH des RIB d'abord (erreurs inline par ligne, tous les blocs
try { // tentes). Le back exige >=1 RIB persiste pour valider une LCR a l'etape 2.
await api.patch(`/clients/${clientId.value}`, {
siren: accounting.siren || null,
accountNumber: accounting.accountNumber || null,
tvaMode: accounting.tvaModeIri,
nTva: accounting.nTva || null,
paymentDelay: accounting.paymentDelayIri,
paymentType: accounting.paymentTypeIri,
bank: isBankRequired.value ? accounting.bankIri : null,
}, { toast: false })
}
catch (error) {
accountingErrors.handleApiError(error, { fallbackMessage: t('commercial.clients.toast.error') })
return
}
// 2) POST/PATCH des RIB (erreurs inline par ligne, tous les blocs tentes).
// Seuls les blocs RIB TOTALEMENT vides sont ignores : un RIB partiel (ex. // Seuls les blocs RIB TOTALEMENT vides sont ignores : un RIB partiel (ex.
// IBAN seul) est soumis -> 422 NotBlank (label / bic / iban) inline. // IBAN seul) est soumis -> 422 NotBlank (label / bic / iban) inline.
const ribHasError = await submitRows( const ribHasError = await submitRows(
@@ -997,6 +980,23 @@ async function submitAccounting(): Promise<void> {
) )
if (ribHasError) return if (ribHasError) return
// 2) PATCH des scalaires comptables (erreurs inline sur leurs champs).
try {
await api.patch(`/clients/${clientId.value}`, {
siren: accounting.siren || null,
accountNumber: accounting.accountNumber || null,
tvaMode: accounting.tvaModeIri,
nTva: accounting.nTva || null,
paymentDelay: accounting.paymentDelayIri,
paymentType: accounting.paymentTypeIri,
bank: isBankRequired.value ? accounting.bankIri : null,
}, { toast: false })
}
catch (error) {
accountingErrors.handleApiError(error, { fallbackMessage: t('commercial.clients.toast.error') })
return
}
completeTab('accounting') completeTab('accounting')
toast.success({ title: t('commercial.clients.toast.updateSuccess') }) toast.success({ title: t('commercial.clients.toast.updateSuccess') })
} }