0df18da00c
- Boutons « ajouter » Adresse/RIB desactives tant que le dernier bloc n'est pas valide (predicats isAddressValid/isRibComplete, reutilises par la validation d'onglet). - Onglet Information : bouton Valider desactive si aucun champ rempli (pas de validation a vide) — creation uniquement, l'edition garde la possibilite de tout vider. - Onglet Contact accessible des la creation du client (onglet Information facultatif).
354 lines
14 KiB
TypeScript
354 lines
14 KiB
TypeScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
addressFlagsFromType,
|
|
addressTypeFromFlags,
|
|
applyProspectExclusivity,
|
|
buildClientFormTabKeys,
|
|
canSelectDeliveryOrBilling,
|
|
canSelectProspect,
|
|
hasAllRequiredAccountingFields,
|
|
hasAtLeastOneInformationField,
|
|
hasAtLeastOneValidContact,
|
|
isAddressValid,
|
|
isBankRequiredForPaymentType,
|
|
isBillingEmailRequired,
|
|
isBlankRow,
|
|
isContactBlank,
|
|
isContactNamed,
|
|
isRibBlank,
|
|
isRibComplete,
|
|
isRibRequiredForPaymentType,
|
|
type AddressValidityDraft,
|
|
type ContactDraft,
|
|
type ContactFillableDraft,
|
|
} 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)', () => {
|
|
it('inclut l onglet accounting si l utilisateur a accounting.view', () => {
|
|
expect(buildClientFormTabKeys(true)).toContain('accounting')
|
|
})
|
|
|
|
it('exclut l onglet accounting sinon (Bureau / Commerciale)', () => {
|
|
expect(buildClientFormTabKeys(false)).not.toContain('accounting')
|
|
})
|
|
|
|
it('a la creation, exclut Statistiques / Rapports / Echanges', () => {
|
|
const keys = buildClientFormTabKeys(true)
|
|
expect(keys).toEqual(['information', 'contact', 'address', 'transport', 'accounting'])
|
|
expect(keys).not.toContain('statistics')
|
|
expect(keys).not.toContain('reports')
|
|
expect(keys).not.toContain('exchanges')
|
|
})
|
|
|
|
it('en modification (includeEditOnlyTabs), ajoute les onglets edit-only en fin', () => {
|
|
const keys = buildClientFormTabKeys(true, { includeEditOnlyTabs: true })
|
|
expect(keys).toEqual([
|
|
'information',
|
|
'contact',
|
|
'address',
|
|
'transport',
|
|
'accounting',
|
|
'statistics',
|
|
'reports',
|
|
'exchanges',
|
|
])
|
|
})
|
|
})
|
|
|
|
describe('isContactNamed (RG-1.05)', () => {
|
|
it('vrai si le prenom est renseigne', () => {
|
|
expect(isContactNamed({ firstName: 'Alice', lastName: null })).toBe(true)
|
|
})
|
|
|
|
it('vrai si le nom est renseigne', () => {
|
|
expect(isContactNamed({ firstName: null, lastName: 'Martin' })).toBe(true)
|
|
})
|
|
|
|
it('faux si les deux sont vides ou espaces uniquement', () => {
|
|
expect(isContactNamed({ firstName: null, lastName: null })).toBe(false)
|
|
expect(isContactNamed({ firstName: ' ', lastName: '' })).toBe(false)
|
|
})
|
|
})
|
|
|
|
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)', () => {
|
|
it('faux sur une liste vide', () => {
|
|
expect(hasAtLeastOneValidContact([])).toBe(false)
|
|
})
|
|
|
|
it('faux si aucun contact n a de nom ni prenom', () => {
|
|
const contacts: ContactDraft[] = [
|
|
{ firstName: null, lastName: null },
|
|
{ firstName: '', lastName: ' ' },
|
|
]
|
|
expect(hasAtLeastOneValidContact(contacts)).toBe(false)
|
|
})
|
|
|
|
it('vrai des qu un contact a un nom ou un prenom', () => {
|
|
const contacts: ContactDraft[] = [
|
|
{ firstName: null, lastName: null },
|
|
{ firstName: 'Bob', lastName: null },
|
|
]
|
|
expect(hasAtLeastOneValidContact(contacts)).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('exclusivite Prospect / Livraison / Facturation (RG-1.06/07/08)', () => {
|
|
it('Prospect est selectionnable tant que ni Livraison ni Facturation', () => {
|
|
expect(canSelectProspect({ isProspect: false, isDelivery: false, isBilling: false })).toBe(true)
|
|
expect(canSelectProspect({ isProspect: false, isDelivery: true, isBilling: false })).toBe(false)
|
|
expect(canSelectProspect({ isProspect: false, isDelivery: false, isBilling: true })).toBe(false)
|
|
})
|
|
|
|
it('Livraison / Facturation selectionnables tant que pas Prospect', () => {
|
|
expect(canSelectDeliveryOrBilling({ isProspect: false, isDelivery: false, isBilling: false })).toBe(true)
|
|
expect(canSelectDeliveryOrBilling({ isProspect: true, isDelivery: false, isBilling: false })).toBe(false)
|
|
})
|
|
|
|
it('cocher Prospect efface Livraison et Facturation', () => {
|
|
const next = applyProspectExclusivity(
|
|
{ isProspect: false, isDelivery: true, isBilling: true },
|
|
'isProspect',
|
|
true,
|
|
)
|
|
expect(next).toEqual({ isProspect: true, isDelivery: false, isBilling: false })
|
|
})
|
|
|
|
it('cocher Livraison efface Prospect', () => {
|
|
const next = applyProspectExclusivity(
|
|
{ isProspect: true, isDelivery: false, isBilling: false },
|
|
'isDelivery',
|
|
true,
|
|
)
|
|
expect(next).toEqual({ isProspect: false, isDelivery: true, isBilling: false })
|
|
})
|
|
|
|
it('cocher Facturation efface Prospect mais conserve Livraison', () => {
|
|
const next = applyProspectExclusivity(
|
|
{ isProspect: true, isDelivery: true, isBilling: false },
|
|
'isBilling',
|
|
true,
|
|
)
|
|
expect(next).toEqual({ isProspect: false, isDelivery: true, isBilling: true })
|
|
})
|
|
|
|
it('decocher un drapeau ne reactive rien d autre', () => {
|
|
const next = applyProspectExclusivity(
|
|
{ isProspect: false, isDelivery: true, isBilling: true },
|
|
'isBilling',
|
|
false,
|
|
)
|
|
expect(next).toEqual({ isProspect: false, isDelivery: true, isBilling: false })
|
|
})
|
|
})
|
|
|
|
describe('isBillingEmailRequired (RG-1.11)', () => {
|
|
it('obligatoire uniquement si Facturation est coche', () => {
|
|
expect(isBillingEmailRequired({ isProspect: false, isDelivery: false, isBilling: true })).toBe(true)
|
|
expect(isBillingEmailRequired({ isProspect: false, isDelivery: true, isBilling: false })).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('type d\'adresse (Select front) <-> drapeaux back', () => {
|
|
it('addressFlagsFromType mappe chaque type vers les bons drapeaux', () => {
|
|
expect(addressFlagsFromType('prospect')).toEqual({ isProspect: true, isDelivery: false, isBilling: false })
|
|
expect(addressFlagsFromType('delivery')).toEqual({ isProspect: false, isDelivery: true, isBilling: false })
|
|
expect(addressFlagsFromType('billing')).toEqual({ isProspect: false, isDelivery: false, isBilling: true })
|
|
expect(addressFlagsFromType('delivery_billing')).toEqual({ isProspect: false, isDelivery: true, isBilling: true })
|
|
})
|
|
|
|
it('addressTypeFromFlags reconstruit le type (Prospect prioritaire, livraison+facturation groupes)', () => {
|
|
expect(addressTypeFromFlags({ isProspect: true, isDelivery: false, isBilling: false })).toBe('prospect')
|
|
expect(addressTypeFromFlags({ isProspect: false, isDelivery: true, isBilling: false })).toBe('delivery')
|
|
expect(addressTypeFromFlags({ isProspect: false, isDelivery: false, isBilling: true })).toBe('billing')
|
|
expect(addressTypeFromFlags({ isProspect: false, isDelivery: true, isBilling: true })).toBe('delivery_billing')
|
|
})
|
|
|
|
it('addressTypeFromFlags retourne null quand aucun drapeau (amorce vierge -> bouton bloque)', () => {
|
|
expect(addressTypeFromFlags({ isProspect: false, isDelivery: false, isBilling: false })).toBeNull()
|
|
})
|
|
|
|
it('aller-retour type -> drapeaux -> type stable pour les 4 types', () => {
|
|
for (const type of ['prospect', 'delivery', 'billing', 'delivery_billing'] as const) {
|
|
expect(addressTypeFromFlags(addressFlagsFromType(type))).toBe(type)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('regles type de reglement (RG-1.12 / RG-1.13)', () => {
|
|
it('banque obligatoire si VIREMENT', () => {
|
|
expect(isBankRequiredForPaymentType('VIREMENT')).toBe(true)
|
|
expect(isBankRequiredForPaymentType('LCR')).toBe(false)
|
|
expect(isBankRequiredForPaymentType(null)).toBe(false)
|
|
})
|
|
|
|
it('RIB obligatoire si LCR', () => {
|
|
expect(isRibRequiredForPaymentType('LCR')).toBe(true)
|
|
expect(isRibRequiredForPaymentType('VIREMENT')).toBe(false)
|
|
expect(isRibRequiredForPaymentType(null)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('hasAllRequiredAccountingFields (RG-1.30)', () => {
|
|
const complete = {
|
|
siren: '123456789',
|
|
accountNumber: '00012345678',
|
|
nTva: 'FR12345678901',
|
|
tvaModeIri: '/api/tva_modes/1',
|
|
paymentDelayIri: '/api/payment_delays/1',
|
|
paymentTypeIri: '/api/payment_types/1',
|
|
}
|
|
|
|
it('vrai quand les six champs obligatoires sont remplis', () => {
|
|
expect(hasAllRequiredAccountingFields(complete)).toBe(true)
|
|
})
|
|
|
|
it('faux si un champ est manquant (null ou vide apres trim)', () => {
|
|
expect(hasAllRequiredAccountingFields({ ...complete, siren: null })).toBe(false)
|
|
expect(hasAllRequiredAccountingFields({ ...complete, accountNumber: ' ' })).toBe(false)
|
|
expect(hasAllRequiredAccountingFields({ ...complete, tvaModeIri: null })).toBe(false)
|
|
expect(hasAllRequiredAccountingFields({ ...complete, paymentTypeIri: null })).toBe(false)
|
|
})
|
|
|
|
it('faux quand tout est vide (onglet non rempli)', () => {
|
|
expect(hasAllRequiredAccountingFields({
|
|
siren: null,
|
|
accountNumber: null,
|
|
nTva: null,
|
|
tvaModeIri: null,
|
|
paymentDelayIri: null,
|
|
paymentTypeIri: null,
|
|
})).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('hasAtLeastOneInformationField (pas de validation a vide a la creation)', () => {
|
|
const blank = {
|
|
description: null,
|
|
competitors: null,
|
|
foundedAt: null,
|
|
employeesCount: null,
|
|
revenueAmount: null,
|
|
profitAmount: null,
|
|
directorName: null,
|
|
}
|
|
|
|
it('faux quand aucun champ n\'est rempli (onglet vierge)', () => {
|
|
expect(hasAtLeastOneInformationField(blank)).toBe(false)
|
|
expect(hasAtLeastOneInformationField({ ...blank, description: ' ' })).toBe(false)
|
|
})
|
|
|
|
it('vrai des qu\'un champ porte une valeur', () => {
|
|
expect(hasAtLeastOneInformationField({ ...blank, description: 'Acteur majeur' })).toBe(true)
|
|
expect(hasAtLeastOneInformationField({ ...blank, employeesCount: '42' })).toBe(true)
|
|
expect(hasAtLeastOneInformationField({ ...blank, foundedAt: '2020-01-01' })).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('isAddressValid (gating « + Adresse » + validation onglet)', () => {
|
|
/** Adresse de livraison valide (type + site + categorie ; pas de facturation). */
|
|
function validDelivery(): AddressValidityDraft {
|
|
return {
|
|
isProspect: false,
|
|
isDelivery: true,
|
|
isBilling: false,
|
|
categoryIris: ['/api/client_categories/1'],
|
|
siteIris: ['/api/sites/1'],
|
|
billingEmail: null,
|
|
}
|
|
}
|
|
|
|
it('vrai quand type + >= 1 site + >= 1 categorie (sans facturation)', () => {
|
|
expect(isAddressValid(validDelivery())).toBe(true)
|
|
})
|
|
|
|
it('faux si aucun drapeau (type d\'adresse non renseigne, amorce vierge)', () => {
|
|
expect(isAddressValid({ ...validDelivery(), isDelivery: false })).toBe(false)
|
|
})
|
|
|
|
it('faux si aucun site (RG-1.10)', () => {
|
|
expect(isAddressValid({ ...validDelivery(), siteIris: [] })).toBe(false)
|
|
})
|
|
|
|
it('faux si aucune categorie', () => {
|
|
expect(isAddressValid({ ...validDelivery(), categoryIris: [] })).toBe(false)
|
|
})
|
|
|
|
it('RG-1.11 : email de facturation obligatoire quand l\'adresse est de facturation', () => {
|
|
const billing: AddressValidityDraft = { ...validDelivery(), isBilling: true }
|
|
expect(isAddressValid({ ...billing, billingEmail: null })).toBe(false)
|
|
expect(isAddressValid({ ...billing, billingEmail: ' ' })).toBe(false)
|
|
expect(isAddressValid({ ...billing, billingEmail: 'facture@acme.fr' })).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('isRibComplete (gating « + RIB » + RG-1.13)', () => {
|
|
it('vrai quand label + BIC + IBAN sont remplis', () => {
|
|
expect(isRibComplete({ label: 'Compte courant', bic: 'BNPAFRPP', iban: 'FR1420041010050500013M02606' })).toBe(true)
|
|
})
|
|
|
|
it('faux si un champ manque (null ou vide apres trim)', () => {
|
|
expect(isRibComplete({ label: null, bic: 'BNPAFRPP', iban: 'FR14...' })).toBe(false)
|
|
expect(isRibComplete({ label: 'Compte', bic: ' ', iban: 'FR14...' })).toBe(false)
|
|
expect(isRibComplete({ label: 'Compte', bic: 'BNPAFRPP', iban: null })).toBe(false)
|
|
})
|
|
|
|
it('faux pour un bloc totalement vide (amorce)', () => {
|
|
expect(isRibComplete({ label: null, bic: null, iban: null })).toBe(false)
|
|
})
|
|
})
|