From 4d20583e1b3efdc2eed0f0596f44aecdaa2832b9 Mon Sep 17 00:00:00 2001 From: tristan Date: Thu, 11 Jun 2026 12:00:37 +0200 Subject: [PATCH] fix(commercial) : conserver le RIB au changement de type de reglement hors-LCR (ERP-121) Le passage d'un tiers de LCR vers virement (ou autre) supprimait ses RIB en base via le front (DELETE differe). Le RIB est une coordonnee bancaire du tiers, decouplee du mode de reglement : on le conserve desormais pour un eventuel retour en LCR. Clients ET fournisseurs (new.vue / [id]/edit.vue) : - onPaymentTypeChange ne marque plus les RIB existants pour suppression et ne vide plus la saisie ; les RIB sont seulement masques (visibleRibs) et reapparaissent tels quels au retour LCR. - submitAccounting ne (re)soumet les RIB que sous LCR ; seules les suppressions EXPLICITES (corbeille d'un bloc) restent en DELETE. Consultation ([id]/index.vue) : RIB dormants totalement masques hors-LCR, via le helper pur type-safe paymentTypeCodeOf (clientConsultation / supplierConsultation) + tests Vitest. Aucune modification back (RG LCR -> >=1 RIB deja correcte, rien n'interdit un RIB sur un tiers non-LCR) ni migration. --- .../commercial/pages/clients/[id]/edit.vue | 94 ++++++++++--------- .../commercial/pages/clients/[id]/index.vue | 16 +++- .../modules/commercial/pages/clients/new.vue | 68 ++++++++------ .../commercial/pages/suppliers/[id]/edit.vue | 85 +++++++++-------- .../commercial/pages/suppliers/[id]/index.vue | 14 ++- .../commercial/pages/suppliers/new.vue | 62 ++++++------ .../__tests__/clientConsultation.spec.ts | 15 +++ .../__tests__/supplierConsultation.spec.ts | 15 +++ .../commercial/utils/clientConsultation.ts | 15 +++ .../commercial/utils/supplierConsultation.ts | 15 +++ 10 files changed, 252 insertions(+), 147 deletions(-) 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 @@