diff --git a/frontend/modules/commercial/pages/clients/[id]/edit.vue b/frontend/modules/commercial/pages/clients/[id]/edit.vue index 33d6d97..86604b4 100644 --- a/frontend/modules/commercial/pages/clients/[id]/edit.vue +++ b/frontend/modules/commercial/pages/clients/[id]/edit.vue @@ -912,17 +912,16 @@ const visibleRibs = computed(() => isRibRequired.value ? ribs.value : []) function onPaymentTypeChange(value: string | number | null): void { accounting.paymentTypeIri = value === null ? null : String(value) if (!isBankRequired.value) accounting.bankIri = null - // Les RIB n'ont de sens que pour une LCR (RG-1.13) : on amorce un bloc vide - // quand LCR est choisi, sinon on vide la liste — les RIB deja persistes sont - // marques pour suppression serveur au prochain enregistrement. + // ERP-121 : un RIB est une coordonnee bancaire du client, decouplee du mode de + // reglement. Au passage hors-LCR on ne SUPPRIME plus les RIB existants : ils + // restent en base, simplement masques a l'ecran (visibleRibs = []), et + // reapparaissent tels quels si l'on repasse en LCR. Seule la corbeille d'un + // bloc (askRemoveRib) retire reellement un RIB. if (isRibRequired.value) { if (ribs.value.length === 0) ribs.value.push(emptyRib()) } else { - for (const rib of ribs.value) { - if (rib.id != null) removedRibIds.value.push(rib.id) - } - ribs.value = [] + // Hors-LCR : on nettoie seulement les erreurs inline (plus affichees). ribErrors.value = [] } } @@ -951,50 +950,58 @@ function askRemoveRib(index: number): void { /** * Valide l'onglet Comptabilite : POST/PATCH des RIB sur la sous-ressource PUIS * PATCH des scalaires (groupe client:write:accounting, exige accounting.manage cote - * back) PUIS DELETE des RIB retires. Les RIB crees d'abord : le back valide RG-1.13 - * (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). + * back) PUIS DELETE des RIB explicitement retires. Les RIB crees d'abord : le back + * valide RG-1.13 (LCR => au moins un RIB persiste) sur le PATCH scalaires. + * + * ERP-121 : les RIB ne sont (re)soumis QUE sous LCR — hors-LCR ce sont des + * coordonnees dormantes conservees telles quelles, masquees a l'ecran et jamais + * re-ecrites. `removedRibIds` ne contient plus que les suppressions EXPLICITES + * (corbeille d'un bloc, toujours sous LCR), plus l'auto-suppression au changement + * de type de reglement. Aucun champ main/information dans le payload (mode strict + * RG-1.28 : sinon 403 sur tout le payload). */ async function submitAccounting(): Promise { if (accountingReadonly.value || tabSubmitting.value) return tabSubmitting.value = true accountingErrors.clearErrors() try { - // 1) POST/PATCH des RIB d'abord (erreurs inline par ligne, tous les blocs - // tentes). Le back exige >=1 RIB persiste pour valider une LCR a l'etape 2. + // 1) POST/PATCH des RIB d'abord — UNIQUEMENT sous LCR (erreurs inline par + // ligne, tous les blocs tentes). Le back exige >=1 RIB persiste pour valider + // une LCR a l'etape 2. Hors-LCR (ERP-121), les RIB sont des coordonnees + // dormantes : rien d'editable n'est affiche, on ne les re-soumet pas. // On ne saute une amorce neuve vide QUE s'il reste un autre RIB soumettable : // sinon (ex. l'unique RIB existant supprime, remplace par un bloc vide), on la // soumet pour declencher la 422 NotBlank inline plutot que de laisser le DELETE // echouer en « dernier RIB d'une LCR » (message plat sans propertyPath). - const hasSubmittableRib = ribs.value.some(r => r.id !== null || !isRibBlank(r)) - const ribHasError = await submitRows( - ribs.value, - ribErrors, - async (rib) => { - // Edition d'un RIB existant : champ requis vide envoye en `''` (NotBlank - // 422) au lieu d'etre omis (sinon le PATCH garderait l'ancienne valeur). - const body = buildRibPayload(rib, { forUpdate: rib.id !== null }) - if (rib.id === null) { - const created = await api.post<{ id: number }>( - `/clients/${clientId}/ribs`, - body, - { headers: { Accept: 'application/ld+json' }, toast: false }, - ) - rib.id = created.id - } - else { - await api.patch(`/client_ribs/${rib.id}`, body, { toast: false }) - } - }, - error => showError(error), - // On ne saute une amorce neuve (id null) totalement vide que si un autre RIB - // est soumettable. Un RIB existant vide est toujours soumis -> 422 NotBlank - // inline (sinon la modif serait perdue en silence avec un faux toast succes). - rib => hasSubmittableRib && rib.id === null && isRibBlank(rib), - ) - if (ribHasError) return + if (isRibRequired.value) { + const hasSubmittableRib = ribs.value.some(r => r.id !== null || !isRibBlank(r)) + const ribHasError = await submitRows( + ribs.value, + ribErrors, + async (rib) => { + // Edition d'un RIB existant : champ requis vide envoye en `''` (NotBlank + // 422) au lieu d'etre omis (sinon le PATCH garderait l'ancienne valeur). + const body = buildRibPayload(rib, { forUpdate: rib.id !== null }) + if (rib.id === null) { + const created = await api.post<{ id: number }>( + `/clients/${clientId}/ribs`, + body, + { headers: { Accept: 'application/ld+json' }, toast: false }, + ) + rib.id = created.id + } + else { + await api.patch(`/client_ribs/${rib.id}`, body, { toast: false }) + } + }, + error => showError(error), + // On ne saute une amorce neuve (id null) totalement vide que si un autre RIB + // est soumettable. Un RIB existant vide est toujours soumis -> 422 NotBlank + // inline (sinon la modif serait perdue en silence avec un faux toast succes). + rib => hasSubmittableRib && rib.id === null && isRibBlank(rib), + ) + if (ribHasError) return + } // 2) PATCH des scalaires comptables (erreurs inline sur leurs champs). try { @@ -1005,8 +1012,9 @@ async function submitAccounting(): Promise { 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). + // 3) DELETE des RIB explicitement retires (corbeille d'un bloc) : APRES le + // PATCH scalaires (le guard back refuse la suppression du dernier RIB d'une + // LCR). ERP-121 : plus aucune suppression automatique au passage hors-LCR. for (const id of removedRibIds.value) { await api.delete(`/client_ribs/${id}`, {}, { toast: false }) } diff --git a/frontend/modules/commercial/pages/clients/[id]/index.vue b/frontend/modules/commercial/pages/clients/[id]/index.vue index 51c3925..cfa09ce 100644 --- a/frontend/modules/commercial/pages/clients/[id]/index.vue +++ b/frontend/modules/commercial/pages/clients/[id]/index.vue @@ -280,7 +280,7 @@