feat(transport) : saisie assistée QUALIMAT + champs conditionnels (ERP-166)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m8s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m36s

This commit is contained in:
2026-06-16 17:22:25 +02:00
parent f1b18cfbbe
commit f70e701854
7 changed files with 738 additions and 48 deletions
@@ -189,3 +189,153 @@ describe('useCarrierForm', () => {
expect(mockPatch).toHaveBeenCalledWith('/carriers/9', { liotPlates: 'AA-123-BB' }, { toast: false })
})
})
describe('useCarrierForm — champs conditionnels (ERP-166)', () => {
beforeEach(() => {
mockPost.mockReset()
mockPatch.mockReset()
})
it('cas LIOT (insensible à la casse) : masque la certification, payload réduit', () => {
const form = useCarrierForm()
form.main.name = 'liot'
expect(form.isLiot.value).toBe(true)
expect(form.showCertification.value).toBe(false)
form.main.liotPlates = 'AA-123-BB ; CC-456-DD'
expect(form.buildMainPayload()).toEqual({
name: 'liot',
isChartered: false,
liotPlates: 'AA-123-BB ; CC-456-DD',
})
})
it('LIOT masque les champs conditionnels (affrètement / décharge)', () => {
const form = useCarrierForm()
form.main.name = 'LIOT'
form.main.isChartered = true
form.main.certificationType = 'AUTRE'
expect(form.showCharteredFields.value).toBe(false)
expect(form.showDischarge.value).toBe(false)
})
it('RG-4.03 affrété : indexation / contenant / volume visibles et dans le payload', () => {
const form = useCarrierForm()
form.main.name = 'Acme'
form.main.certificationType = 'GMP_PLUS'
form.main.isChartered = true
expect(form.showCharteredFields.value).toBe(true)
form.main.indexationRate = '5'
form.main.containerType = 'BENNE'
form.main.volumeM3 = '30'
expect(form.buildMainPayload()).toEqual({
name: 'Acme',
certificationType: 'GMP_PLUS',
isChartered: true,
indexationRate: '5',
containerType: 'BENNE',
volumeM3: '30',
})
})
it('RG-4.03 affrété mais champs vides : omis du payload (422 NotBlank back)', () => {
const form = useCarrierForm()
form.main.name = 'Acme'
form.main.certificationType = 'GMP_PLUS'
form.main.isChartered = true
expect(form.buildMainPayload()).toEqual({
name: 'Acme',
certificationType: 'GMP_PLUS',
isChartered: true,
})
})
it('RG-4.02 AUTRE : décharge visible + dischargeDocument dans le payload si IRI résolu', () => {
const form = useCarrierForm()
form.main.name = 'Acme'
form.main.certificationType = 'AUTRE'
expect(form.showDischarge.value).toBe(true)
form.main.dischargeDocumentIri = '/api/uploaded_documents/7'
expect(form.buildMainPayload()).toMatchObject({ dischargeDocument: '/api/uploaded_documents/7' })
})
})
describe('useCarrierForm — copie QUALIMAT (ERP-166)', () => {
const QUALIMAT_ROW = {
'@id': '/api/qualimat_carriers/42',
id: '42',
name: 'TRANSPORTS QUALIMAT',
siret: '12345678900012',
address: '1 rue du Port',
postalCode: '86000',
city: 'Poitiers',
validityDate: '2027-01-15',
status: 'VALIDE',
}
beforeEach(() => {
mockPost.mockReset()
mockPatch.mockReset()
})
it('copie name + certificationType=QUALIMAT (readonly) + IRI + adresse, sans PATCH avant création', async () => {
const form = useCarrierForm()
const ok = await form.applyQualimatSelection(QUALIMAT_ROW)
expect(ok).toBe(true)
expect(mockPatch).not.toHaveBeenCalled()
expect(form.main.name).toBe('TRANSPORTS QUALIMAT')
expect(form.main.certificationType).toBe('QUALIMAT')
expect(form.main.qualimatCarrierIri).toBe('/api/qualimat_carriers/42')
expect(form.isQualimat.value).toBe(true)
expect(form.certificationReadonly.value).toBe(true)
expect(form.qualimatAddress.value).toEqual({
country: 'France',
postalCode: '86000',
city: 'Poitiers',
street: '1 rue du Port',
})
})
it('après création : PATCH /carriers/{id} avec qualimatCarrier + name + certification', async () => {
mockPost.mockResolvedValueOnce({ id: 9, name: 'X', certificationType: 'GMP_PLUS' })
mockPatch.mockResolvedValueOnce({})
const form = useCarrierForm()
form.main.name = 'X'
form.main.certificationType = 'GMP_PLUS'
await form.submitMain()
const ok = await form.applyQualimatSelection(QUALIMAT_ROW)
expect(ok).toBe(true)
expect(mockPatch).toHaveBeenCalledWith(
'/carriers/9',
{
qualimatCarrier: '/api/qualimat_carriers/42',
name: 'TRANSPORTS QUALIMAT',
certificationType: 'QUALIMAT',
},
{ toast: false },
)
})
it('buildMainPayload inclut qualimatCarrier + certificationType QUALIMAT après intégration', async () => {
const form = useCarrierForm()
form.main.name = 'Acme'
await form.applyQualimatSelection(QUALIMAT_ROW)
expect(form.buildMainPayload()).toMatchObject({
qualimatCarrier: '/api/qualimat_carriers/42',
certificationType: 'QUALIMAT',
})
})
})
@@ -0,0 +1,55 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
/**
* Tests de la saisie assistée QUALIMAT (M4 Transport, ERP-166 — RG-4.01).
*
* `useQualimatSearch` interroge `GET /api/qualimat_carriers?search=`. On vérifie le
* CONTRAT (pas le timing du debounce, couvert par `debounce.test.ts`) via `fetchNow` :
* - ressource ciblée + paramètre `search` (trimé) + header `Accept: application/ld+json` ;
* - consommation de l'enveloppe Hydra (`member`) ;
* - échec réseau → résultats vidés, pas de throw (recherche non bloquante).
*/
const mockGet = vi.hoisted(() => vi.fn())
vi.stubGlobal('useApi', () => ({
get: mockGet,
post: vi.fn(),
put: vi.fn(),
patch: vi.fn(),
delete: vi.fn(),
}))
const { useQualimatSearch } = await import('../useQualimatSearch')
describe('useQualimatSearch', () => {
beforeEach(() => {
mockGet.mockReset()
})
it('fetchNow cible /qualimat_carriers (search trimé, ld+json) et consomme member', async () => {
mockGet.mockResolvedValueOnce({
member: [{ '@id': '/api/qualimat_carriers/1', id: '1', name: 'ACME', validityDate: '2027-01-01' }],
})
const q = useQualimatSearch()
await q.fetchNow(' acme ')
expect(mockGet).toHaveBeenCalledWith(
'/qualimat_carriers',
{ search: 'acme' },
{ headers: { Accept: 'application/ld+json' }, toast: false },
)
expect(q.results.value).toHaveLength(1)
expect(q.results.value[0]?.name).toBe('ACME')
expect(q.loading.value).toBe(false)
})
it('échec réseau : résultats vidés, pas de throw', async () => {
mockGet.mockRejectedValueOnce(new Error('network'))
const q = useQualimatSearch()
await expect(q.fetchNow('x')).resolves.toBeUndefined()
expect(q.results.value).toEqual([])
expect(q.loading.value).toBe(false)
})
})