import { describe, it, expect } from 'vitest' import { addressFlagsFromType, addressTypeFromFlags, applyProspectExclusivity, buildClientFormTabKeys, canSelectDeliveryOrBilling, canSelectProspect, hasAllRequiredAccountingFields, hasAtLeastOneValidContact, isBankRequiredForPaymentType, isBillingEmailRequired, isBlankRow, isContactBlank, isContactNamed, isRibBlank, isRibRequiredForPaymentType, 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) }) })