fix(commercial) : ne sauter que les blocs contact/RIB totalement vides (bloc partiel sans nom -> 422 inline, ERP-110)
This commit is contained in:
@@ -414,7 +414,9 @@ import {
|
|||||||
hasAtLeastOneValidContact,
|
hasAtLeastOneValidContact,
|
||||||
isBankRequiredForPaymentType,
|
isBankRequiredForPaymentType,
|
||||||
isBillingEmailRequired,
|
isBillingEmailRequired,
|
||||||
|
isContactBlank,
|
||||||
isContactNamed,
|
isContactNamed,
|
||||||
|
isRibBlank,
|
||||||
isRibRequiredForPaymentType,
|
isRibRequiredForPaymentType,
|
||||||
} from '~/modules/commercial/utils/clientFormRules'
|
} from '~/modules/commercial/utils/clientFormRules'
|
||||||
import {
|
import {
|
||||||
@@ -742,8 +744,9 @@ async function submitContacts(): Promise<void> {
|
|||||||
}
|
}
|
||||||
removedContactIds.value = []
|
removedContactIds.value = []
|
||||||
|
|
||||||
// On tente TOUS les blocs (collecte des erreurs par index, ERP-110) ; les
|
// On tente TOUS les blocs (collecte des erreurs par index, ERP-110). Seuls
|
||||||
// blocs vides (ni nom ni prenom) sont ignores.
|
// les blocs TOTALEMENT vides sont ignores : un bloc partiellement rempli
|
||||||
|
// sans nom (email seul) est soumis -> 422 RG-1.05 inline sous le bloc.
|
||||||
const hasError = await submitRows(
|
const hasError = await submitRows(
|
||||||
contacts.value,
|
contacts.value,
|
||||||
contactErrors,
|
contactErrors,
|
||||||
@@ -763,7 +766,7 @@ async function submitContacts(): Promise<void> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
error => showError(error),
|
error => showError(error),
|
||||||
contact => !isContactNamed(contact),
|
contact => isContactBlank(contact),
|
||||||
)
|
)
|
||||||
// Tant qu'un bloc reste en erreur : pas de toast succes.
|
// Tant qu'un bloc reste en erreur : pas de toast succes.
|
||||||
if (hasError) return
|
if (hasError) return
|
||||||
@@ -917,8 +920,9 @@ async function submitAccounting(): Promise<void> {
|
|||||||
}
|
}
|
||||||
removedRibIds.value = []
|
removedRibIds.value = []
|
||||||
|
|
||||||
// 2) POST/PATCH des RIB (erreurs inline par ligne, tous les blocs tentes —
|
// 2) POST/PATCH des RIB (erreurs inline par ligne, tous les blocs tentes).
|
||||||
// les blocs RIB incomplets sont ignores).
|
// Seuls les blocs RIB TOTALEMENT vides sont ignores : un RIB partiel (ex.
|
||||||
|
// IBAN seul) est soumis -> 422 NotBlank (label / bic / iban) inline.
|
||||||
const ribHasError = await submitRows(
|
const ribHasError = await submitRows(
|
||||||
ribs.value,
|
ribs.value,
|
||||||
ribErrors,
|
ribErrors,
|
||||||
@@ -937,7 +941,7 @@ async function submitAccounting(): Promise<void> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
error => showError(error),
|
error => showError(error),
|
||||||
rib => !ribIsComplete(rib),
|
rib => isRibBlank(rib),
|
||||||
)
|
)
|
||||||
if (ribHasError) return
|
if (ribHasError) return
|
||||||
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
toast.success({ title: t('commercial.clients.toast.updateSuccess') })
|
||||||
|
|||||||
@@ -385,7 +385,9 @@ import {
|
|||||||
hasAtLeastOneValidContact,
|
hasAtLeastOneValidContact,
|
||||||
isBankRequiredForPaymentType,
|
isBankRequiredForPaymentType,
|
||||||
isBillingEmailRequired,
|
isBillingEmailRequired,
|
||||||
|
isContactBlank,
|
||||||
isContactNamed,
|
isContactNamed,
|
||||||
|
isRibBlank,
|
||||||
isRibRequiredForPaymentType,
|
isRibRequiredForPaymentType,
|
||||||
} from '~/modules/commercial/utils/clientFormRules'
|
} from '~/modules/commercial/utils/clientFormRules'
|
||||||
import {
|
import {
|
||||||
@@ -677,8 +679,9 @@ async function submitContacts(): Promise<void> {
|
|||||||
if (clientId.value === null || !canValidateContacts.value || tabSubmitting.value) return
|
if (clientId.value === null || !canValidateContacts.value || tabSubmitting.value) return
|
||||||
tabSubmitting.value = true
|
tabSubmitting.value = true
|
||||||
try {
|
try {
|
||||||
// On tente TOUS les blocs (collecte des erreurs par index, ERP-110) ; les
|
// On tente TOUS les blocs (collecte des erreurs par index, ERP-110). Seuls
|
||||||
// blocs vides (ni nom ni prenom) sont ignores.
|
// les blocs TOTALEMENT vides sont ignores : un bloc partiellement rempli
|
||||||
|
// sans nom (email seul) est soumis -> 422 RG-1.05 inline sous le bloc.
|
||||||
const hasError = await submitRows(
|
const hasError = await submitRows(
|
||||||
contacts.value,
|
contacts.value,
|
||||||
contactErrors,
|
contactErrors,
|
||||||
@@ -705,7 +708,7 @@ async function submitContacts(): Promise<void> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
error => toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) }),
|
error => toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) }),
|
||||||
contact => !isContactNamed(contact),
|
contact => isContactBlank(contact),
|
||||||
)
|
)
|
||||||
// Tant qu'un bloc reste en erreur : pas de validation d'onglet ni de toast succes.
|
// Tant qu'un bloc reste en erreur : pas de validation d'onglet ni de toast succes.
|
||||||
if (hasError) return
|
if (hasError) return
|
||||||
@@ -901,8 +904,9 @@ async function submitAccounting(): Promise<void> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2) POST/PATCH des RIB (erreurs inline par ligne, tous les blocs tentes —
|
// 2) POST/PATCH des RIB (erreurs inline par ligne, tous les blocs tentes).
|
||||||
// les blocs RIB incomplets sont ignores).
|
// Seuls les blocs RIB TOTALEMENT vides sont ignores : un RIB partiel (ex.
|
||||||
|
// IBAN seul) est soumis -> 422 NotBlank (label / bic / iban) inline.
|
||||||
const ribHasError = await submitRows(
|
const ribHasError = await submitRows(
|
||||||
ribs.value,
|
ribs.value,
|
||||||
ribErrors,
|
ribErrors,
|
||||||
@@ -921,7 +925,7 @@ async function submitAccounting(): Promise<void> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
error => toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) }),
|
error => toast.error({ title: t('commercial.clients.toast.error'), message: apiErrorMessage(error) }),
|
||||||
rib => !ribIsComplete(rib),
|
rib => isRibBlank(rib),
|
||||||
)
|
)
|
||||||
if (ribHasError) return
|
if (ribHasError) return
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,27 @@ import {
|
|||||||
hasAtLeastOneValidContact,
|
hasAtLeastOneValidContact,
|
||||||
isBankRequiredForPaymentType,
|
isBankRequiredForPaymentType,
|
||||||
isBillingEmailRequired,
|
isBillingEmailRequired,
|
||||||
|
isBlankRow,
|
||||||
|
isContactBlank,
|
||||||
isContactNamed,
|
isContactNamed,
|
||||||
|
isRibBlank,
|
||||||
isRibRequiredForPaymentType,
|
isRibRequiredForPaymentType,
|
||||||
type ContactDraft,
|
type ContactDraft,
|
||||||
|
type ContactFillableDraft,
|
||||||
} from '../clientFormRules'
|
} from '../clientFormRules'
|
||||||
|
|
||||||
|
/** Bloc contact totalement vide (amorce par defaut). */
|
||||||
|
function blankContact(): ContactFillableDraft {
|
||||||
|
return {
|
||||||
|
firstName: null,
|
||||||
|
lastName: null,
|
||||||
|
jobTitle: null,
|
||||||
|
phonePrimary: null,
|
||||||
|
phoneSecondary: null,
|
||||||
|
email: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe('buildClientFormTabKeys (gating onglet Comptabilite + onglets edit-only)', () => {
|
describe('buildClientFormTabKeys (gating onglet Comptabilite + onglets edit-only)', () => {
|
||||||
it('inclut l onglet accounting si l utilisateur a accounting.view', () => {
|
it('inclut l onglet accounting si l utilisateur a accounting.view', () => {
|
||||||
expect(buildClientFormTabKeys(true)).toContain('accounting')
|
expect(buildClientFormTabKeys(true)).toContain('accounting')
|
||||||
@@ -59,6 +75,49 @@ describe('isContactNamed (RG-1.05)', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('isBlankRow (primitive : toutes les valeurs vides)', () => {
|
||||||
|
it('vrai si toutes les valeurs sont nulles / vides / espaces', () => {
|
||||||
|
expect(isBlankRow([null, undefined, '', ' '])).toBe(true)
|
||||||
|
expect(isBlankRow([])).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('faux des qu une valeur porte un caractere non-espace', () => {
|
||||||
|
expect(isBlankRow([null, 'x', ''])).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('isRibBlank (bloc RIB totalement vide vs partiellement rempli)', () => {
|
||||||
|
it('vrai si label / bic / iban sont tous vides', () => {
|
||||||
|
expect(isRibBlank({ label: null, bic: null, iban: null })).toBe(true)
|
||||||
|
expect(isRibBlank({ label: ' ', bic: '', iban: null })).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('faux si un IBAN seul est saisi (bloc a soumettre -> 422 NotBlank inline)', () => {
|
||||||
|
expect(isRibBlank({ label: null, bic: null, iban: 'FR1420041010050500013M02606' })).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('faux si seul le libelle est saisi', () => {
|
||||||
|
expect(isRibBlank({ label: 'Compte courant', bic: null, iban: null })).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('isContactBlank (bloc totalement vide vs partiellement rempli)', () => {
|
||||||
|
it('vrai si aucun champ saisissable n est rempli', () => {
|
||||||
|
expect(isContactBlank(blankContact())).toBe(true)
|
||||||
|
expect(isContactBlank({ ...blankContact(), firstName: ' ', email: '' })).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('faux si un email seul est saisi (bloc a soumettre -> 422 RG-1.05 inline)', () => {
|
||||||
|
expect(isContactBlank({ ...blankContact(), email: 'jean@acme.fr' })).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('faux si seul un telephone, une fonction ou un nom est saisi', () => {
|
||||||
|
expect(isContactBlank({ ...blankContact(), phonePrimary: '0612345678' })).toBe(false)
|
||||||
|
expect(isContactBlank({ ...blankContact(), jobTitle: 'Directeur' })).toBe(false)
|
||||||
|
expect(isContactBlank({ ...blankContact(), firstName: 'Alice' })).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('hasAtLeastOneValidContact (RG-1.14)', () => {
|
describe('hasAtLeastOneValidContact (RG-1.14)', () => {
|
||||||
it('faux sur une liste vide', () => {
|
it('faux sur une liste vide', () => {
|
||||||
expect(hasAtLeastOneValidContact([])).toBe(false)
|
expect(hasAtLeastOneValidContact([])).toBe(false)
|
||||||
|
|||||||
@@ -86,6 +86,58 @@ export function hasAtLeastOneValidContact(contacts: ContactDraft[]): boolean {
|
|||||||
return contacts.some(isContactNamed)
|
return contacts.some(isContactNamed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive reutilisable : vrai si TOUTES les valeurs fournies sont vides (null /
|
||||||
|
* undefined / espaces uniquement). Sert a detecter un bloc de collection
|
||||||
|
* totalement vide (amorce non remplie). Un bloc qui porte la moindre donnee
|
||||||
|
* n'est PAS « blank » : il doit etre soumis pour declencher sa 422 inline plutot
|
||||||
|
* que d'etre saute silencieusement.
|
||||||
|
*/
|
||||||
|
export function isBlankRow(values: (string | null | undefined)[]): boolean {
|
||||||
|
return values.every(value => !isFilled(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Champs saisissables d'un bloc contact (pour detecter un bloc totalement vide). */
|
||||||
|
export interface ContactFillableDraft extends ContactDraft {
|
||||||
|
jobTitle: string | null
|
||||||
|
phonePrimary: string | null
|
||||||
|
phoneSecondary: string | null
|
||||||
|
email: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vrai si AUCUN champ saisissable du bloc contact n'est rempli. Distingue un bloc
|
||||||
|
* d'amorce vide (a ignorer au submit) d'un bloc partiellement rempli sans nom
|
||||||
|
* (email / telephone / fonction seul) : ce dernier doit etre soumis pour
|
||||||
|
* declencher la 422 RG-1.05 (« prenom ou nom obligatoire ») affichee inline.
|
||||||
|
*/
|
||||||
|
export function isContactBlank(contact: ContactFillableDraft): boolean {
|
||||||
|
return isBlankRow([
|
||||||
|
contact.firstName,
|
||||||
|
contact.lastName,
|
||||||
|
contact.jobTitle,
|
||||||
|
contact.phonePrimary,
|
||||||
|
contact.phoneSecondary,
|
||||||
|
contact.email,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Champs saisissables d'un bloc RIB (pour detecter un bloc totalement vide). */
|
||||||
|
export interface RibFillableDraft {
|
||||||
|
label: string | null
|
||||||
|
bic: string | null
|
||||||
|
iban: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vrai si AUCUN champ du bloc RIB n'est rempli. Un RIB partiellement rempli (ex.
|
||||||
|
* IBAN seul) n'est PAS « blank » : il doit etre soumis pour declencher les 422
|
||||||
|
* NotBlank (label / bic / iban) inline plutot que d'etre saute silencieusement.
|
||||||
|
*/
|
||||||
|
export function isRibBlank(rib: RibFillableDraft): boolean {
|
||||||
|
return isBlankRow([rib.label, rib.bic, rib.iban])
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RG-1.06/07/08 : une adresse de prospection est exclusive d'une adresse de
|
* RG-1.06/07/08 : une adresse de prospection est exclusive d'une adresse de
|
||||||
* livraison/facturation. Prospect n'est selectionnable que si ni Livraison ni
|
* livraison/facturation. Prospect n'est selectionnable que si ni Livraison ni
|
||||||
|
|||||||
Reference in New Issue
Block a user