import { describe, expect, it } from 'vitest' import { canEditClient, categoryOptionsOf, contactOptionsOf, iriOf, mapAccountingDraft, mapAddressToDraft, mapAddressView, mapContactToDraft, mapRibToDraft, referentialOptionOf, relationOf, showArchiveAction, showRestoreAction, siteOptionsOf, type ClientDetail, } from '../clientConsultation' describe('iriOf', () => { it('retourne l\'@id d\'une relation embarquee (objet)', () => { expect(iriOf({ '@id': '/api/payment_types/10', code: 'LCR' })).toBe('/api/payment_types/10') }) it('retourne la chaine telle quelle si la relation est deja un IRI', () => { expect(iriOf('/api/banks/3')).toBe('/api/banks/3') }) it('retourne null pour une relation absente (null / undefined / skip_null_values)', () => { expect(iriOf(null)).toBeNull() expect(iriOf(undefined)).toBeNull() }) }) describe('relationOf', () => { it('detecte une relation distributeur et expose son nom', () => { const client = { distributor: { '@id': '/api/clients/15', companyName: 'DISTRIB GRAND SUD-OUEST' } } as ClientDetail expect(relationOf(client)).toEqual({ type: 'distributeur', name: 'DISTRIB GRAND SUD-OUEST' }) }) it('detecte une relation courtier et expose son nom', () => { const client = { broker: { '@id': '/api/clients/16', companyName: 'CABINET LEONARD' } } as ClientDetail expect(relationOf(client)).toEqual({ type: 'courtier', name: 'CABINET LEONARD' }) }) it('retourne type null quand aucune relation n\'est posee (cles omises)', () => { expect(relationOf({} as ClientDetail)).toEqual({ type: null, name: null }) }) }) describe('mapContactToDraft', () => { it('formate les telephones en XX XX XX XX XX et conserve l\'iri', () => { const draft = mapContactToDraft({ '@id': '/api/client_contacts/18', id: 18, firstName: 'Sophie', lastName: 'Léonard', jobTitle: 'Gérante', phonePrimary: '0549112233', email: 'sophie@x.fr', }) expect(draft.id).toBe(18) expect(draft.iri).toBe('/api/client_contacts/18') expect(draft.phonePrimary).toBe('05 49 11 22 33') expect(draft.hasSecondaryPhone).toBe(false) }) it('revele le 2e telephone quand phoneSecondary est present', () => { const draft = mapContactToDraft({ '@id': '/api/client_contacts/19', id: 19, phonePrimary: '0600000000', phoneSecondary: '0611111111', }) expect(draft.hasSecondaryPhone).toBe(true) expect(draft.phoneSecondary).toBe('06 11 11 11 11') }) }) describe('mapAddressToDraft', () => { it('extrait les iris de sites / categories / contacts (objets ou chaines)', () => { const draft = mapAddressToDraft({ '@id': '/api/client_addresses/18', id: 18, country: 'France', postalCode: '86100', city: 'Châtellerault', street: '5 rue des Courtiers', billingEmail: 'factures@x.fr', isProspect: false, isDelivery: false, isBilling: true, sites: [{ '@id': '/api/sites/4', name: 'Chatellerault', color: '#056CF2' }], categories: [{ '@id': '/api/categories/3', code: 'SECTEUR' }], contacts: [{ '@id': '/api/client_contacts/18' }, '/api/client_contacts/20'], }) expect(draft.siteIris).toEqual(['/api/sites/4']) expect(draft.categoryIris).toEqual(['/api/categories/3']) expect(draft.contactIris).toEqual(['/api/client_contacts/18', '/api/client_contacts/20']) expect(draft.isBilling).toBe(true) expect(draft.city).toBe('Châtellerault') expect(draft.country).toBe('France') }) it('tolere les sous-collections absentes (defaut tableau vide, pays France)', () => { const draft = mapAddressToDraft({ '@id': '/api/client_addresses/9', id: 9 }) expect(draft.siteIris).toEqual([]) expect(draft.categoryIris).toEqual([]) expect(draft.contactIris).toEqual([]) expect(draft.country).toBe('France') expect(draft.isBilling).toBe(false) }) }) describe('mapRibToDraft', () => { it('mappe label / bic / iban et l\'id serveur', () => { const draft = mapRibToDraft({ '@id': '/api/client_ribs/3', id: 3, label: 'Compte', bic: 'BNPAFRPPXXX', iban: 'FR14...' }) expect(draft).toEqual({ id: 3, label: 'Compte', bic: 'BNPAFRPPXXX', iban: 'FR14...' }) }) }) describe('mapAccountingDraft', () => { it('mappe les scalaires et resout les iris des referentiels embarques', () => { const acc = mapAccountingDraft({ '@id': '/api/clients/1', id: 1, siren: '123456789', accountNumber: '411000', nTva: 'FR123', tvaMode: { '@id': '/api/tva_modes/1' }, paymentDelay: { '@id': '/api/payment_delays/2' }, paymentType: { '@id': '/api/payment_types/10', code: 'LCR' }, bank: { '@id': '/api/banks/3' }, } as ClientDetail) expect(acc).toEqual({ siren: '123456789', accountNumber: '411000', nTva: 'FR123', tvaModeIri: '/api/tva_modes/1', paymentDelayIri: '/api/payment_delays/2', paymentTypeIri: '/api/payment_types/10', bankIri: '/api/banks/3', }) }) it('renvoie des null quand les champs comptables sont absents (sans accounting.view)', () => { const acc = mapAccountingDraft({} as ClientDetail) expect(acc).toEqual({ siren: null, accountNumber: null, nTva: null, tvaModeIri: null, paymentDelayIri: null, paymentTypeIri: null, bankIri: null, }) }) }) describe('options construites depuis l\'embed (role-independantes)', () => { it('categoryOptionsOf expose value=IRI, label=nom, code', () => { expect(categoryOptionsOf([{ '@id': '/api/categories/3', name: 'Secteur', code: 'SECTEUR' }])).toEqual([ { value: '/api/categories/3', label: 'Secteur', code: 'SECTEUR' }, ]) }) it('siteOptionsOf expose value=IRI, label=nom', () => { expect(siteOptionsOf([{ '@id': '/api/sites/4', name: 'Chatellerault', color: '#000' }])).toEqual([ { value: '/api/sites/4', label: 'Chatellerault' }, ]) }) it('contactOptionsOf compose le libelle (nom complet, sinon email)', () => { expect(contactOptionsOf([ { '@id': '/api/client_contacts/1', id: 1, firstName: 'Jean', lastName: 'Dupont' }, { '@id': '/api/client_contacts/2', id: 2, email: 'a@b.fr' }, ])).toEqual([ { value: '/api/client_contacts/1', label: 'Jean Dupont' }, { value: '/api/client_contacts/2', label: 'a@b.fr' }, ]) }) it('referentialOptionOf : option unique depuis l\'embed, vide pour IRI nu / absent', () => { expect(referentialOptionOf({ '@id': '/api/payment_types/10', label: 'LCR' })).toEqual([ { value: '/api/payment_types/10', label: 'LCR' }, ]) expect(referentialOptionOf('/api/banks/3')).toEqual([]) expect(referentialOptionOf(null)).toEqual([]) }) it('mapAddressView assemble brouillon + options propres a l\'adresse', () => { const view = mapAddressView({ '@id': '/api/client_addresses/18', id: 18, city: 'Châtellerault', sites: [{ '@id': '/api/sites/4', name: 'Chatellerault' }], categories: [{ '@id': '/api/categories/3', name: 'Secteur', code: 'SECTEUR' }], }) expect(view.draft.id).toBe(18) expect(view.siteOptions).toEqual([{ value: '/api/sites/4', label: 'Chatellerault' }]) expect(view.categoryOptions).toEqual([{ value: '/api/categories/3', label: 'Secteur', code: 'SECTEUR' }]) }) }) describe('canEditClient', () => { const can = (granted: string[]) => (codes: string[]) => codes.some(c => granted.includes(c)) it('visible pour manage', () => { expect(canEditClient(can(['commercial.clients.manage']))).toBe(true) }) it('visible pour accounting.manage (role Compta)', () => { expect(canEditClient(can(['commercial.clients.accounting.manage']))).toBe(true) }) it('masque sans aucune des deux permissions (role Usine)', () => { expect(canEditClient(can(['commercial.clients.view']))).toBe(false) }) }) describe('showArchiveAction / showRestoreAction', () => { const can = (granted: string[]) => (code: string) => granted.includes(code) it('Archiver : visible avec la permission archive ET client non archive', () => { expect(showArchiveAction(can(['commercial.clients.archive']), false)).toBe(true) expect(showArchiveAction(can(['commercial.clients.archive']), true)).toBe(false) expect(showArchiveAction(can([]), false)).toBe(false) }) it('Restaurer : visible avec la permission archive ET client archive', () => { expect(showRestoreAction(can(['commercial.clients.archive']), true)).toBe(true) expect(showRestoreAction(can(['commercial.clients.archive']), false)).toBe(false) expect(showRestoreAction(can([]), true)).toBe(false) }) })