fix(transport) : certification obligatoire en pré-validation front, sauf cas LIOT (ERP-166)
This commit is contained in:
@@ -573,7 +573,8 @@
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"nameRequired": "Le nom du transporteur est obligatoire."
|
||||
"nameRequired": "Le nom du transporteur est obligatoire.",
|
||||
"certificationRequired": "Le type de certification est obligatoire."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,30 @@ describe('useCarrierForm', () => {
|
||||
expect(form.mainErrors.errors.name).toBe('transport.carriers.form.errors.nameRequired')
|
||||
})
|
||||
|
||||
it('front : certification vide (hors LIOT) → erreur inline sur certificationType, pas de POST', async () => {
|
||||
const form = useCarrierForm()
|
||||
form.main.name = 'Acme'
|
||||
// certificationType laissé null → bloqué côté front (RG-4.01).
|
||||
|
||||
const created = await form.submitMain()
|
||||
|
||||
expect(created).toBe(false)
|
||||
expect(mockPost).not.toHaveBeenCalled()
|
||||
expect(form.mainErrors.errors.certificationType).toBe('transport.carriers.form.errors.certificationRequired')
|
||||
})
|
||||
|
||||
it('front : cas LIOT → certification non requise (aucune erreur de certification)', async () => {
|
||||
mockPost.mockResolvedValueOnce({ id: 5, name: 'LIOT', certificationType: null })
|
||||
const form = useCarrierForm()
|
||||
form.main.name = 'LIOT'
|
||||
form.main.liotPlates = 'AA-123-BB'
|
||||
|
||||
const created = await form.submitMain()
|
||||
|
||||
expect(created).toBe(true)
|
||||
expect(form.mainErrors.errors.certificationType).toBeUndefined()
|
||||
})
|
||||
|
||||
it('POST /carriers avec Accept ld+json, verrouille et bascule sur Qualimat', async () => {
|
||||
mockPost.mockResolvedValueOnce({ id: 42, name: 'TRANSPORTS ACME', certificationType: 'GMP_PLUS' })
|
||||
const form = useCarrierForm()
|
||||
@@ -93,15 +117,11 @@ describe('useCarrierForm', () => {
|
||||
expect(form.unlockedIndex.value).toBe(0)
|
||||
})
|
||||
|
||||
it('payload : omet name et certificationType vides, garde isChartered', async () => {
|
||||
mockPost.mockRejectedValueOnce({ response: { status: 422, _data: { violations: [] } } })
|
||||
it('buildMainPayload : omet certificationType vide, garde isChartered', () => {
|
||||
const form = useCarrierForm()
|
||||
form.main.name = 'X' // nom présent pour passer le pré-check front
|
||||
// certificationType laissé null → omis pour que la 422 « obligatoire » porte.
|
||||
form.main.name = 'X'
|
||||
|
||||
await form.submitMain()
|
||||
|
||||
const body = mockPost.mock.calls[0]?.[1] as Record<string, unknown>
|
||||
const body = form.buildMainPayload()
|
||||
expect(body).toEqual({ name: 'X', isChartered: false })
|
||||
expect('certificationType' in body).toBe(false)
|
||||
})
|
||||
@@ -119,20 +139,24 @@ describe('useCarrierForm', () => {
|
||||
expect(form.mainLocked.value).toBe(false)
|
||||
})
|
||||
|
||||
it('422 : mappe les violations serveur inline par champ', async () => {
|
||||
it('422 : mappe les violations serveur inline par champ (RG conditionnelle back)', async () => {
|
||||
// Champ re-validé côté back (RG-4.03) : le pré-check front laisse passer le POST,
|
||||
// la 422 mappe inline sur le champ via son propertyPath.
|
||||
mockPost.mockRejectedValueOnce({
|
||||
response: {
|
||||
status: 422,
|
||||
_data: { violations: [{ propertyPath: 'certificationType', message: 'Le type de certification est obligatoire.' }] },
|
||||
_data: { violations: [{ propertyPath: 'indexationRate', message: "Le taux d'indexation est obligatoire pour un transporteur affrété." }] },
|
||||
},
|
||||
})
|
||||
const form = useCarrierForm()
|
||||
form.main.name = 'Sans Certif'
|
||||
form.main.name = 'Acme'
|
||||
form.main.certificationType = 'GMP_PLUS'
|
||||
form.main.isChartered = true
|
||||
|
||||
const created = await form.submitMain()
|
||||
|
||||
expect(created).toBe(false)
|
||||
expect(form.mainErrors.errors.certificationType).toBe('Le type de certification est obligatoire.')
|
||||
expect(form.mainErrors.errors.indexationRate).toBe("Le taux d'indexation est obligatoire pour un transporteur affrété.")
|
||||
expect(form.mainLocked.value).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
@@ -95,10 +95,9 @@ export function useCarrierForm() {
|
||||
|
||||
/**
|
||||
* Validation FRONT du formulaire principal : seul le nom est requis côté front
|
||||
* (RG-4.01). Le back reste la couche autoritaire (ERP-101) — la certification
|
||||
* obligatoire (sauf LIOT) et les RG conditionnelles sont re-validées serveur et
|
||||
* remontées en 422 inline, sans pré-check front (qui devrait connaître le cas
|
||||
* LIOT, hors périmètre ERP-165).
|
||||
* (RG-4.01) : nom requis, et certification requise hors cas LIOT (où elle est
|
||||
* masquée). Le back reste la couche autoritaire (ERP-101) — les RG conditionnelles
|
||||
* (affrètement, décharge AUTRE) sont re-validées serveur et remontées en 422 inline.
|
||||
*/
|
||||
function validateMainFront(): boolean {
|
||||
let valid = true
|
||||
@@ -106,6 +105,11 @@ export function useCarrierForm() {
|
||||
mainErrors.setError('name', t('transport.carriers.form.errors.nameRequired'))
|
||||
valid = false
|
||||
}
|
||||
// RG-4.01 : la certification est obligatoire SAUF en cas LIOT (champ masqué).
|
||||
if (!isLiot.value && !main.certificationType) {
|
||||
mainErrors.setError('certificationType', t('transport.carriers.form.errors.certificationRequired'))
|
||||
valid = false
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user