4d20583e1b
Le passage d'un tiers de LCR vers virement (ou autre) supprimait ses RIB en base via le front (DELETE differe). Le RIB est une coordonnee bancaire du tiers, decouplee du mode de reglement : on le conserve desormais pour un eventuel retour en LCR. Clients ET fournisseurs (new.vue / [id]/edit.vue) : - onPaymentTypeChange ne marque plus les RIB existants pour suppression et ne vide plus la saisie ; les RIB sont seulement masques (visibleRibs) et reapparaissent tels quels au retour LCR. - submitAccounting ne (re)soumet les RIB que sous LCR ; seules les suppressions EXPLICITES (corbeille d'un bloc) restent en DELETE. Consultation ([id]/index.vue) : RIB dormants totalement masques hors-LCR, via le helper pur type-safe paymentTypeCodeOf (clientConsultation / supplierConsultation) + tests Vitest. Aucune modification back (RG LCR -> >=1 RIB deja correcte, rien n'interdit un RIB sur un tiers non-LCR) ni migration.
251 lines
10 KiB
TypeScript
251 lines
10 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import {
|
|
canEditClient,
|
|
categoryOptionsOf,
|
|
contactOptionsOf,
|
|
iriOf,
|
|
mapAccountingDraft,
|
|
mapAddressToDraft,
|
|
mapAddressView,
|
|
mapContactToDraft,
|
|
mapRibToDraft,
|
|
paymentTypeCodeOf,
|
|
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)
|
|
})
|
|
})
|
|
|
|
describe('paymentTypeCodeOf (ERP-121 : RIB masques hors-LCR en consultation)', () => {
|
|
it('retourne le code metier quand le type de reglement est embarque', () => {
|
|
expect(paymentTypeCodeOf({ '@id': '/api/payment_types/1', code: 'LCR' })).toBe('LCR')
|
|
expect(paymentTypeCodeOf({ '@id': '/api/payment_types/2', code: 'VIREMENT' })).toBe('VIREMENT')
|
|
})
|
|
|
|
it('retourne null pour un IRI nu, un objet sans code, ou une relation absente', () => {
|
|
expect(paymentTypeCodeOf('/api/payment_types/1')).toBeNull()
|
|
expect(paymentTypeCodeOf({ '@id': '/api/payment_types/1' })).toBeNull()
|
|
expect(paymentTypeCodeOf(null)).toBeNull()
|
|
expect(paymentTypeCodeOf(undefined)).toBeNull()
|
|
})
|
|
})
|