fix(front) : suppression immediate des sous-ressources + regle poubelle (ERP-172)
- DELETE immediat des sous-ressources (contacts / adresses / RIB) a la confirmation de la modale sur les ecrans de modification M1 / M2 / M3, au lieu d'un DELETE differe qui ne partait jamais sans re-validation de l'onglet. Helper partage removeCollectionRow (+ tests) ; le mecanisme differe (removed*Ids + boucles dans submit*) devenu mort est supprime. - Affichage de la poubelle des blocs de collection unifie sur les 3 modules via isRowRemovable : visible seulement s'il reste un AUTRE bloc deja enregistre (id en base). Empeche de supprimer un bloc tant que rien n'est sauvegarde, et de supprimer son dernier bloc enregistre. Applique aux ecrans new + edit (contacts / adresses / RIB).
This commit is contained in:
@@ -157,12 +157,16 @@
|
||||
<!-- Onglet Contact -->
|
||||
<template #contact>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<!-- ERP-172 : poubelle visible seulement s'il reste un AUTRE bloc deja
|
||||
enregistre (id en base) — cf. isRowRemovable. Empeche de supprimer un
|
||||
bloc tant que rien n'est sauvegarde, et de supprimer son dernier
|
||||
bloc enregistre. -->
|
||||
<ClientContactBlock
|
||||
v-for="(contact, index) in contacts"
|
||||
:key="contact.id ?? `new-${index}`"
|
||||
:model-value="contact"
|
||||
:title="t('commercial.clients.form.contact.title', { n: index + 1 })"
|
||||
:removable="contacts.length > 1"
|
||||
:removable="isRowRemovable(contacts, index)"
|
||||
:readonly="businessReadonly"
|
||||
:errors="contactErrors[index]"
|
||||
@update:model-value="(v) => contacts[index] = v"
|
||||
@@ -199,7 +203,7 @@
|
||||
:site-options="siteOptions"
|
||||
:contact-options="contactOptions"
|
||||
:country-options="countryOptions"
|
||||
:removable="addresses.length > 1"
|
||||
:removable="isRowRemovable(addresses, index)"
|
||||
:readonly="businessReadonly"
|
||||
:errors="addressErrors[index]"
|
||||
@update:model-value="(v) => addresses[index] = v"
|
||||
@@ -304,7 +308,7 @@
|
||||
class="relative bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
|
||||
>
|
||||
<MalioButtonIcon
|
||||
v-if="!accountingReadonly && visibleRibs.length > 1"
|
||||
v-if="!accountingReadonly && isRowRemovable(visibleRibs, index)"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="absolute top-3 right-3"
|
||||
@@ -440,6 +444,7 @@ import {
|
||||
type RibFormDraft,
|
||||
} from '~/modules/commercial/types/clientForm'
|
||||
import { extractApiErrorMessage } from '~/shared/utils/api'
|
||||
import { isRowRemovable, removeCollectionRow } from '~/shared/utils/collectionRow'
|
||||
import { readHistoryTab } from '~/shared/utils/historyTab'
|
||||
|
||||
// Masques de saisie (la normalisation finale reste serveur).
|
||||
@@ -490,10 +495,6 @@ const contacts = ref<ContactFormDraft[]>([])
|
||||
const addresses = ref<AddressFormDraft[]>([])
|
||||
const ribs = ref<RibFormDraft[]>([])
|
||||
|
||||
// Ids des sous-ressources existantes supprimees (DELETE differe au « Valider »).
|
||||
const removedContactIds = ref<number[]>([])
|
||||
const removedAddressIds = ref<number[]>([])
|
||||
const removedRibIds = ref<number[]>([])
|
||||
|
||||
const mainSubmitting = ref(false)
|
||||
const tabSubmitting = ref(false)
|
||||
@@ -754,32 +755,31 @@ function addContact(): void {
|
||||
if (canAddContact.value) contacts.value.push(emptyContact())
|
||||
}
|
||||
|
||||
// ERP-172 : DELETE immediat de la sous-ressource a la confirmation de la modale
|
||||
// (et non plus differe au « Enregistrer »). Bloc jamais persiste (id null) : retrait
|
||||
// local. Echec serveur : bloc conserve + erreur remontee.
|
||||
function askRemoveContact(index: number): void {
|
||||
askConfirm(t('commercial.clients.form.confirmDelete.contact'), () => {
|
||||
const removed = contacts.value[index]
|
||||
if (removed?.id != null) removedContactIds.value.push(removed.id)
|
||||
contacts.value.splice(index, 1)
|
||||
contactErrors.value.splice(index, 1)
|
||||
// Garde au moins un bloc visible (cf. amorce a l'hydratation).
|
||||
if (contacts.value.length === 0) contacts.value.push(emptyContact())
|
||||
})
|
||||
askConfirm(t('commercial.clients.form.confirmDelete.contact'), () => removeCollectionRow({
|
||||
rows: contacts.value,
|
||||
errors: contactErrors.value,
|
||||
index,
|
||||
endpoint: '/client_contacts',
|
||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||
makeEmpty: emptyContact,
|
||||
onError: showError,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide l'onglet Contact : DELETE des contacts retires (existants), puis
|
||||
* POST/PATCH des blocs restants sur la sous-ressource. Strictement scope a la
|
||||
* collection contacts (endpoints client_contact dedies).
|
||||
* Valide l'onglet Contact : POST/PATCH des blocs restants sur la sous-ressource.
|
||||
* Strictement scope a la collection contacts (endpoints client_contact dedies). La
|
||||
* suppression est traitee a part, en DELETE immediat (askRemoveContact, ERP-172).
|
||||
*/
|
||||
async function submitContacts(): Promise<void> {
|
||||
if (businessReadonly.value || tabSubmitting.value) return
|
||||
tabSubmitting.value = true
|
||||
contactErrors.value = []
|
||||
try {
|
||||
for (const id of removedContactIds.value) {
|
||||
await api.delete(`/client_contacts/${id}`, {}, { toast: false })
|
||||
}
|
||||
removedContactIds.value = []
|
||||
|
||||
// RG-1.14 : au moins un contact requis. Si l'onglet ne contient QUE des
|
||||
// amorces neuves vides (ex. tous les contacts existants supprimes), on ne
|
||||
// les skippe pas -> le back renvoie la 422 RG-1.05 « prénom ou nom
|
||||
@@ -836,14 +836,15 @@ function addAddress(): void {
|
||||
}
|
||||
|
||||
function askRemoveAddress(index: number): void {
|
||||
askConfirm(t('commercial.clients.form.confirmDelete.address'), () => {
|
||||
const removed = addresses.value[index]
|
||||
if (removed?.id != null) removedAddressIds.value.push(removed.id)
|
||||
addresses.value.splice(index, 1)
|
||||
addressErrors.value.splice(index, 1)
|
||||
// Garde au moins un bloc visible (cf. amorce a l'hydratation).
|
||||
if (addresses.value.length === 0) addresses.value.push(emptyAddress())
|
||||
})
|
||||
askConfirm(t('commercial.clients.form.confirmDelete.address'), () => removeCollectionRow({
|
||||
rows: addresses.value,
|
||||
errors: addressErrors.value,
|
||||
index,
|
||||
endpoint: '/client_addresses',
|
||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||
makeEmpty: emptyAddress,
|
||||
onError: showError,
|
||||
}))
|
||||
}
|
||||
|
||||
function onAddressDegraded(): void {
|
||||
@@ -855,17 +856,12 @@ function onAddressDegraded(): void {
|
||||
})
|
||||
}
|
||||
|
||||
/** Valide l'onglet Adresse : DELETE des adresses retirees puis POST/PATCH. */
|
||||
/** Valide l'onglet Adresse : POST/PATCH des blocs restants (suppression en DELETE immediat, ERP-172). */
|
||||
async function submitAddresses(): Promise<void> {
|
||||
if (businessReadonly.value || tabSubmitting.value) return
|
||||
tabSubmitting.value = true
|
||||
addressErrors.value = []
|
||||
try {
|
||||
for (const id of removedAddressIds.value) {
|
||||
await api.delete(`/client_addresses/${id}`, {}, { toast: false })
|
||||
}
|
||||
removedAddressIds.value = []
|
||||
|
||||
// On tente TOUS les blocs d'adresse (collecte des erreurs par index, ERP-110).
|
||||
const hasError = await submitRows(
|
||||
addresses.value,
|
||||
@@ -937,29 +933,32 @@ function addRib(): void {
|
||||
if (canAddRib.value) ribs.value.push(emptyRib())
|
||||
}
|
||||
|
||||
// ERP-172 : DELETE immediat du RIB. Le back refuse la suppression du dernier RIB
|
||||
// d'une LCR (RG-1.13) -> 409 remonte via showError (message back), bloc conserve.
|
||||
function askRemoveRib(index: number): void {
|
||||
askConfirm(t('commercial.clients.form.confirmDelete.rib'), () => {
|
||||
const removed = ribs.value[index]
|
||||
if (removed?.id != null) removedRibIds.value.push(removed.id)
|
||||
ribs.value.splice(index, 1)
|
||||
ribErrors.value.splice(index, 1)
|
||||
// Garde au moins un bloc RIB visible (cf. amorce a l'hydratation).
|
||||
if (ribs.value.length === 0) ribs.value.push(emptyRib())
|
||||
})
|
||||
askConfirm(t('commercial.clients.form.confirmDelete.rib'), () => removeCollectionRow({
|
||||
rows: ribs.value,
|
||||
errors: ribErrors.value,
|
||||
index,
|
||||
endpoint: '/client_ribs',
|
||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||
makeEmpty: emptyRib,
|
||||
onError: showError,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 explicitement retires. Les RIB crees d'abord : le back
|
||||
* valide RG-1.13 (LCR => au moins un RIB persiste) sur le PATCH scalaires.
|
||||
* back). Les RIB crees d'abord : le back valide RG-1.13 (LCR => au moins un RIB
|
||||
* persiste) sur le PATCH scalaires.
|
||||
*
|
||||
* ERP-172 : la suppression d'un RIB est traitee en DELETE immediat (askRemoveRib),
|
||||
* plus de DELETE differe ici.
|
||||
* 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).
|
||||
* re-ecrites. Aucun champ main/information dans le payload (mode strict RG-1.28 :
|
||||
* sinon 403 sur tout le payload).
|
||||
*/
|
||||
async function submitAccounting(): Promise<void> {
|
||||
if (accountingReadonly.value || tabSubmitting.value) return
|
||||
@@ -1013,14 +1012,6 @@ async function submitAccounting(): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
// 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 })
|
||||
}
|
||||
removedRibIds.value = []
|
||||
|
||||
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
@@ -156,12 +156,16 @@
|
||||
<!-- Onglet Contact -->
|
||||
<template #contact>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<!-- ERP-172 : poubelle visible seulement s'il reste un AUTRE bloc deja
|
||||
enregistre (id en base) — cf. isRowRemovable. Empeche de supprimer un
|
||||
bloc tant que rien n'est sauvegarde, et de supprimer son dernier
|
||||
bloc enregistre. -->
|
||||
<ClientContactBlock
|
||||
v-for="(contact, index) in contacts"
|
||||
:key="index"
|
||||
:model-value="contact"
|
||||
:title="t('commercial.clients.form.contact.title', { n: index + 1 })"
|
||||
:removable="index > 0"
|
||||
:removable="isRowRemovable(contacts, index)"
|
||||
:readonly="isValidated('contact')"
|
||||
:errors="contactErrors[index]"
|
||||
@update:model-value="(v) => contacts[index] = v"
|
||||
@@ -198,7 +202,7 @@
|
||||
:site-options="referentials.sites.value"
|
||||
:contact-options="contactOptions"
|
||||
:country-options="countryOptions"
|
||||
:removable="index > 0"
|
||||
:removable="isRowRemovable(addresses, index)"
|
||||
:readonly="isValidated('address')"
|
||||
:errors="addressErrors[index]"
|
||||
@update:model-value="(v) => addresses[index] = v"
|
||||
@@ -303,7 +307,7 @@
|
||||
>
|
||||
<!-- ariaLabel via v-bind objet (prop camelCase ; aria-* serait un attribut HTML). -->
|
||||
<MalioButtonIcon
|
||||
v-if="!accountingReadonly && visibleRibs.length > 1"
|
||||
v-if="!accountingReadonly && isRowRemovable(visibleRibs, index)"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="absolute top-3 right-3"
|
||||
@@ -417,6 +421,7 @@ import {
|
||||
type RibFormDraft,
|
||||
} from '~/modules/commercial/types/clientForm'
|
||||
import { extractApiErrorMessage } from '~/shared/utils/api'
|
||||
import { isRowRemovable } from '~/shared/utils/collectionRow'
|
||||
|
||||
// Masques de saisie (la normalisation finale reste serveur).
|
||||
const SIREN_MASK = '#########'
|
||||
|
||||
@@ -126,12 +126,16 @@
|
||||
<!-- Onglet Contacts -->
|
||||
<template #contacts>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<!-- ERP-172 : poubelle visible seulement s'il reste un AUTRE bloc deja
|
||||
enregistre (id en base) — cf. isRowRemovable. Empeche de supprimer un
|
||||
bloc tant que rien n'est sauvegarde, et de supprimer son dernier
|
||||
bloc enregistre. -->
|
||||
<SupplierContactBlock
|
||||
v-for="(contact, index) in contacts"
|
||||
:key="contact.id ?? `new-${index}`"
|
||||
:model-value="contact"
|
||||
:title="t('commercial.suppliers.form.contact.title', { n: index + 1 })"
|
||||
:removable="contacts.length > 1"
|
||||
:removable="isRowRemovable(contacts, index)"
|
||||
:readonly="businessReadonly"
|
||||
:errors="contactErrors[index]"
|
||||
@update:model-value="(v) => contacts[index] = v"
|
||||
@@ -168,7 +172,7 @@
|
||||
:site-options="siteOptions"
|
||||
:contact-options="contactOptions"
|
||||
:country-options="countryOptions"
|
||||
:removable="addresses.length > 1"
|
||||
:removable="isRowRemovable(addresses, index)"
|
||||
:readonly="businessReadonly"
|
||||
:errors="addressErrors[index]"
|
||||
@update:model-value="(v) => addresses[index] = v"
|
||||
@@ -273,7 +277,7 @@
|
||||
class="relative bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
|
||||
>
|
||||
<MalioButtonIcon
|
||||
v-if="!accountingReadonly && visibleRibs.length > 1"
|
||||
v-if="!accountingReadonly && isRowRemovable(visibleRibs, index)"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="absolute top-3 right-3"
|
||||
@@ -407,6 +411,7 @@ import {
|
||||
type SupplierRibFormDraft,
|
||||
} from '~/modules/commercial/types/supplierForm'
|
||||
import { extractApiErrorMessage } from '~/shared/utils/api'
|
||||
import { isRowRemovable, removeCollectionRow } from '~/shared/utils/collectionRow'
|
||||
import { readHistoryTab } from '~/shared/utils/historyTab'
|
||||
|
||||
// Masques de saisie (la normalisation finale reste serveur).
|
||||
@@ -456,10 +461,6 @@ const contacts = ref<SupplierContactFormDraft[]>([])
|
||||
const addresses = ref<SupplierAddressFormDraft[]>([])
|
||||
const ribs = ref<SupplierRibFormDraft[]>([])
|
||||
|
||||
// Ids des sous-ressources existantes supprimees (DELETE differe au « Valider »).
|
||||
const removedContactIds = ref<number[]>([])
|
||||
const removedAddressIds = ref<number[]>([])
|
||||
const removedRibIds = ref<number[]>([])
|
||||
|
||||
const mainSubmitting = ref(false)
|
||||
const tabSubmitting = ref(false)
|
||||
@@ -653,32 +654,31 @@ function addContact(): void {
|
||||
if (canAddContact.value) contacts.value.push(emptyContact())
|
||||
}
|
||||
|
||||
// ERP-172 : DELETE immediat de la sous-ressource a la confirmation de la modale
|
||||
// (et non plus differe au « Enregistrer »). Bloc jamais persiste (id null) : retrait
|
||||
// local. Echec serveur : bloc conserve + erreur remontee.
|
||||
function askRemoveContact(index: number): void {
|
||||
askConfirm(t('commercial.suppliers.form.confirmDelete.contact'), () => {
|
||||
const removed = contacts.value[index]
|
||||
if (removed?.id != null) removedContactIds.value.push(removed.id)
|
||||
contacts.value.splice(index, 1)
|
||||
contactErrors.value.splice(index, 1)
|
||||
// Garde au moins un bloc visible (cf. amorce a l'hydratation).
|
||||
if (contacts.value.length === 0) contacts.value.push(emptyContact())
|
||||
})
|
||||
askConfirm(t('commercial.suppliers.form.confirmDelete.contact'), () => removeCollectionRow({
|
||||
rows: contacts.value,
|
||||
errors: contactErrors.value,
|
||||
index,
|
||||
endpoint: '/supplier_contacts',
|
||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||
makeEmpty: emptyContact,
|
||||
onError: showError,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide l'onglet Contacts : DELETE des contacts retires (existants), puis
|
||||
* POST/PATCH des blocs restants sur la sous-ressource. Strictement scope a la
|
||||
* collection contacts (endpoints supplier_contact dedies).
|
||||
* Valide l'onglet Contacts : POST/PATCH des blocs restants sur la sous-ressource.
|
||||
* Strictement scope a la collection contacts (endpoints supplier_contact dedies).
|
||||
* La suppression est traitee a part, en DELETE immediat (askRemoveContact, ERP-172).
|
||||
*/
|
||||
async function submitContacts(): Promise<void> {
|
||||
if (businessReadonly.value || tabSubmitting.value) return
|
||||
tabSubmitting.value = true
|
||||
contactErrors.value = []
|
||||
try {
|
||||
for (const id of removedContactIds.value) {
|
||||
await api.delete(`/supplier_contacts/${id}`, {}, { toast: false })
|
||||
}
|
||||
removedContactIds.value = []
|
||||
|
||||
// RG-2.13 : au moins un contact requis. Si l'onglet ne contient QUE des
|
||||
// amorces neuves vides, on les soumet -> 422 RG-2.04 inline (nom OU prenom).
|
||||
const hasSubmittableContact = contacts.value.some(c => c.id !== null || !isContactBlank(c))
|
||||
@@ -726,14 +726,15 @@ function addAddress(): void {
|
||||
}
|
||||
|
||||
function askRemoveAddress(index: number): void {
|
||||
askConfirm(t('commercial.suppliers.form.confirmDelete.address'), () => {
|
||||
const removed = addresses.value[index]
|
||||
if (removed?.id != null) removedAddressIds.value.push(removed.id)
|
||||
addresses.value.splice(index, 1)
|
||||
addressErrors.value.splice(index, 1)
|
||||
// Garde au moins un bloc visible (cf. amorce a l'hydratation).
|
||||
if (addresses.value.length === 0) addresses.value.push(emptyAddress())
|
||||
})
|
||||
askConfirm(t('commercial.suppliers.form.confirmDelete.address'), () => removeCollectionRow({
|
||||
rows: addresses.value,
|
||||
errors: addressErrors.value,
|
||||
index,
|
||||
endpoint: '/supplier_addresses',
|
||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||
makeEmpty: emptyAddress,
|
||||
onError: showError,
|
||||
}))
|
||||
}
|
||||
|
||||
function onAddressDegraded(): void {
|
||||
@@ -745,17 +746,12 @@ function onAddressDegraded(): void {
|
||||
})
|
||||
}
|
||||
|
||||
/** Valide l'onglet Adresses : DELETE des adresses retirees puis POST/PATCH. */
|
||||
/** Valide l'onglet Adresses : POST/PATCH des blocs restants (suppression en DELETE immediat, ERP-172). */
|
||||
async function submitAddresses(): Promise<void> {
|
||||
if (businessReadonly.value || tabSubmitting.value) return
|
||||
tabSubmitting.value = true
|
||||
addressErrors.value = []
|
||||
try {
|
||||
for (const id of removedAddressIds.value) {
|
||||
await api.delete(`/supplier_addresses/${id}`, {}, { toast: false })
|
||||
}
|
||||
removedAddressIds.value = []
|
||||
|
||||
const hasError = await submitRows(
|
||||
addresses.value,
|
||||
addressErrors,
|
||||
@@ -826,15 +822,18 @@ function addRib(): void {
|
||||
if (canAddRib.value) ribs.value.push(emptyRib())
|
||||
}
|
||||
|
||||
// ERP-172 : DELETE immediat du RIB. Le back refuse la suppression du dernier RIB
|
||||
// d'une LCR (RG-2.08) -> 409 remonte via showError (message back), bloc conserve.
|
||||
function askRemoveRib(index: number): void {
|
||||
askConfirm(t('commercial.suppliers.form.confirmDelete.rib'), () => {
|
||||
const removed = ribs.value[index]
|
||||
if (removed?.id != null) removedRibIds.value.push(removed.id)
|
||||
ribs.value.splice(index, 1)
|
||||
ribErrors.value.splice(index, 1)
|
||||
// Garde au moins un bloc RIB visible (cf. amorce a l'hydratation).
|
||||
if (ribs.value.length === 0) ribs.value.push(emptyRib())
|
||||
})
|
||||
askConfirm(t('commercial.suppliers.form.confirmDelete.rib'), () => removeCollectionRow({
|
||||
rows: ribs.value,
|
||||
errors: ribErrors.value,
|
||||
index,
|
||||
endpoint: '/supplier_ribs',
|
||||
deleteRow: url => api.delete(url, {}, { toast: false }),
|
||||
makeEmpty: emptyRib,
|
||||
onError: showError,
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -843,11 +842,12 @@ function askRemoveRib(index: number): void {
|
||||
* cote back) PUIS DELETE des RIB explicitement retires. Les RIB crees d'abord : le
|
||||
* back valide RG-2.08 (LCR => au moins un RIB persiste) sur le PATCH scalaires.
|
||||
*
|
||||
* ERP-172 : la suppression d'un RIB est traitee en DELETE immediat (askRemoveRib),
|
||||
* plus de DELETE differe ici.
|
||||
* 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). Aucun champ main/information dans le
|
||||
* payload (mode strict RG-2.16 : sinon 403 sur tout le payload).
|
||||
* re-ecrites. Aucun champ main/information dans le payload (mode strict RG-2.16 :
|
||||
* sinon 403 sur tout le payload).
|
||||
*/
|
||||
async function submitAccounting(): Promise<void> {
|
||||
if (accountingReadonly.value || tabSubmitting.value) return
|
||||
@@ -897,14 +897,6 @@ async function submitAccounting(): Promise<void> {
|
||||
return
|
||||
}
|
||||
|
||||
// 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(`/supplier_ribs/${id}`, {}, { toast: false })
|
||||
}
|
||||
removedRibIds.value = []
|
||||
|
||||
toast.success({ title: t('commercial.suppliers.toast.updateSuccess') })
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
@@ -121,12 +121,16 @@
|
||||
<!-- Onglet Contacts -->
|
||||
<template #contacts>
|
||||
<div class="mt-12 flex flex-col gap-6">
|
||||
<!-- ERP-172 : poubelle visible seulement s'il reste un AUTRE bloc deja
|
||||
enregistre (id en base) — cf. isRowRemovable. Empeche de supprimer un
|
||||
bloc tant que rien n'est sauvegarde, et de supprimer son dernier
|
||||
bloc enregistre. -->
|
||||
<SupplierContactBlock
|
||||
v-for="(contact, index) in contacts"
|
||||
:key="index"
|
||||
:model-value="contact"
|
||||
:title="t('commercial.suppliers.form.contact.title', { n: index + 1 })"
|
||||
:removable="index > 0"
|
||||
:removable="isRowRemovable(contacts, index)"
|
||||
:readonly="isValidated('contacts')"
|
||||
:errors="contactErrors[index]"
|
||||
@update:model-value="(v) => contacts[index] = v"
|
||||
@@ -163,7 +167,7 @@
|
||||
:site-options="referentials.sites.value"
|
||||
:contact-options="contactOptions"
|
||||
:country-options="countryOptions"
|
||||
:removable="index > 0"
|
||||
:removable="isRowRemovable(addresses, index)"
|
||||
:readonly="isValidated('addresses')"
|
||||
:errors="addressErrors[index]"
|
||||
@update:model-value="(v) => addresses[index] = v"
|
||||
@@ -267,7 +271,7 @@
|
||||
class="relative bg-white py-4 pl-[28px] pr-[60px] shadow-[0_4px_4px_0_rgba(0,0,0,0.25)]"
|
||||
>
|
||||
<MalioButtonIcon
|
||||
v-if="!accountingReadonly && visibleRibs.length > 1"
|
||||
v-if="!accountingReadonly && isRowRemovable(visibleRibs, index)"
|
||||
icon="mdi:delete-outline"
|
||||
variant="ghost"
|
||||
button-class="absolute top-3 right-3"
|
||||
@@ -380,6 +384,7 @@ import {
|
||||
type SupplierRibFormDraft,
|
||||
} from '~/modules/commercial/types/supplierForm'
|
||||
import { extractApiErrorMessage } from '~/shared/utils/api'
|
||||
import { isRowRemovable } from '~/shared/utils/collectionRow'
|
||||
|
||||
// Masques de saisie (la normalisation finale reste serveur).
|
||||
const SIREN_MASK = '#########'
|
||||
|
||||
Reference in New Issue
Block a user