fix(transport) : pré-validation front des champs conditionnels obligatoires (décharge AUTRE, affrètement) (ERP-166)
This commit is contained in:
@@ -574,7 +574,11 @@
|
|||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"nameRequired": "Le nom du transporteur est obligatoire.",
|
"nameRequired": "Le nom du transporteur est obligatoire.",
|
||||||
"certificationRequired": "Le type de certification est obligatoire."
|
"certificationRequired": "Le type de certification est obligatoire.",
|
||||||
|
"dischargeRequired": "La décharge est obligatoire pour une certification « Autre ».",
|
||||||
|
"indexationRequired": "Le taux d'indexation est obligatoire pour un transporteur affrété.",
|
||||||
|
"containerTypeRequired": "Le type de contenant est obligatoire pour un transporteur affrété.",
|
||||||
|
"volumeRequired": "Le volume est obligatoire pour un transporteur affrété."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,12 +89,61 @@ describe('useCarrierForm', () => {
|
|||||||
expect(form.mainErrors.errors.certificationType).toBeUndefined()
|
expect(form.mainErrors.errors.certificationType).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('front RG-4.02 : certification AUTRE sans décharge → erreur inline, pas de POST', async () => {
|
||||||
|
const form = useCarrierForm()
|
||||||
|
form.main.name = 'Acme'
|
||||||
|
form.main.certificationType = 'AUTRE'
|
||||||
|
// dischargeDocumentIri null (upload non fourni).
|
||||||
|
|
||||||
|
const created = await form.submitMain()
|
||||||
|
|
||||||
|
expect(created).toBe(false)
|
||||||
|
expect(mockPost).not.toHaveBeenCalled()
|
||||||
|
expect(form.mainErrors.errors.dischargeDocument).toBe('transport.carriers.form.errors.dischargeRequired')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('front RG-4.03 : affrété sans indexation / contenant / volume → 3 erreurs inline, pas de POST', async () => {
|
||||||
|
const form = useCarrierForm()
|
||||||
|
form.main.name = 'Acme'
|
||||||
|
form.main.certificationType = 'GMP_PLUS'
|
||||||
|
form.main.isChartered = true
|
||||||
|
|
||||||
|
const created = await form.submitMain()
|
||||||
|
|
||||||
|
expect(created).toBe(false)
|
||||||
|
expect(mockPost).not.toHaveBeenCalled()
|
||||||
|
expect(form.mainErrors.errors.indexationRate).toBe('transport.carriers.form.errors.indexationRequired')
|
||||||
|
expect(form.mainErrors.errors.containerType).toBe('transport.carriers.form.errors.containerTypeRequired')
|
||||||
|
expect(form.mainErrors.errors.volumeM3).toBe('transport.carriers.form.errors.volumeRequired')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('front RG-4.03 : affrété avec tous les champs remplis → POST envoyé', async () => {
|
||||||
|
mockPost.mockResolvedValueOnce({ id: 8, name: 'ACME', certificationType: 'GMP_PLUS' })
|
||||||
|
const form = useCarrierForm()
|
||||||
|
form.main.name = 'Acme'
|
||||||
|
form.main.certificationType = 'GMP_PLUS'
|
||||||
|
form.main.isChartered = true
|
||||||
|
form.main.indexationRate = '5'
|
||||||
|
form.main.containerType = 'BENNE'
|
||||||
|
form.main.volumeM3 = '30'
|
||||||
|
|
||||||
|
const created = await form.submitMain()
|
||||||
|
|
||||||
|
expect(created).toBe(true)
|
||||||
|
expect(mockPost).toHaveBeenCalledTimes(1)
|
||||||
|
expect(mockPost.mock.calls[0]?.[1]).toMatchObject({
|
||||||
|
isChartered: true,
|
||||||
|
indexationRate: '5',
|
||||||
|
containerType: 'BENNE',
|
||||||
|
volumeM3: '30',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('POST /carriers avec Accept ld+json, verrouille et bascule sur Qualimat', async () => {
|
it('POST /carriers avec Accept ld+json, verrouille et bascule sur Qualimat', async () => {
|
||||||
mockPost.mockResolvedValueOnce({ id: 42, name: 'TRANSPORTS ACME', certificationType: 'GMP_PLUS' })
|
mockPost.mockResolvedValueOnce({ id: 42, name: 'TRANSPORTS ACME', certificationType: 'GMP_PLUS' })
|
||||||
const form = useCarrierForm()
|
const form = useCarrierForm()
|
||||||
form.main.name = 'Transports Acme'
|
form.main.name = 'Transports Acme'
|
||||||
form.main.certificationType = 'GMP_PLUS'
|
form.main.certificationType = 'GMP_PLUS'
|
||||||
form.main.isChartered = true
|
|
||||||
|
|
||||||
const created = await form.submitMain()
|
const created = await form.submitMain()
|
||||||
|
|
||||||
@@ -105,7 +154,7 @@ describe('useCarrierForm', () => {
|
|||||||
expect(body).toEqual({
|
expect(body).toEqual({
|
||||||
name: 'Transports Acme',
|
name: 'Transports Acme',
|
||||||
certificationType: 'GMP_PLUS',
|
certificationType: 'GMP_PLUS',
|
||||||
isChartered: true,
|
isChartered: false,
|
||||||
})
|
})
|
||||||
expect(opts).toMatchObject({ toast: false, headers: { Accept: 'application/ld+json' } })
|
expect(opts).toMatchObject({ toast: false, headers: { Accept: 'application/ld+json' } })
|
||||||
|
|
||||||
@@ -130,7 +179,7 @@ describe('useCarrierForm', () => {
|
|||||||
mockPost.mockRejectedValueOnce({ response: { status: 409 } })
|
mockPost.mockRejectedValueOnce({ response: { status: 409 } })
|
||||||
const form = useCarrierForm()
|
const form = useCarrierForm()
|
||||||
form.main.name = 'Doublon'
|
form.main.name = 'Doublon'
|
||||||
form.main.certificationType = 'AUTRE'
|
form.main.certificationType = 'GMP_PLUS'
|
||||||
|
|
||||||
const created = await form.submitMain()
|
const created = await form.submitMain()
|
||||||
|
|
||||||
@@ -139,24 +188,24 @@ describe('useCarrierForm', () => {
|
|||||||
expect(form.mainLocked.value).toBe(false)
|
expect(form.mainLocked.value).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('422 : mappe les violations serveur inline par champ (RG conditionnelle back)', async () => {
|
it('422 : mappe les violations serveur inline par champ', async () => {
|
||||||
// Champ re-validé côté back (RG-4.03) : le pré-check front laisse passer le POST,
|
// Contrainte re-validée uniquement back (ex. longueur du nom) : le pré-check
|
||||||
// la 422 mappe inline sur le champ via son propertyPath.
|
// front passe (nom rempli, certif choisie, non affrété), la 422 mappe inline
|
||||||
|
// sur le champ via son propertyPath.
|
||||||
mockPost.mockRejectedValueOnce({
|
mockPost.mockRejectedValueOnce({
|
||||||
response: {
|
response: {
|
||||||
status: 422,
|
status: 422,
|
||||||
_data: { violations: [{ propertyPath: 'indexationRate', message: "Le taux d'indexation est obligatoire pour un transporteur affrété." }] },
|
_data: { violations: [{ propertyPath: 'name', message: 'Le nom du transporteur ne peut dépasser 255 caractères.' }] },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const form = useCarrierForm()
|
const form = useCarrierForm()
|
||||||
form.main.name = 'Acme'
|
form.main.name = 'Acme'
|
||||||
form.main.certificationType = 'GMP_PLUS'
|
form.main.certificationType = 'GMP_PLUS'
|
||||||
form.main.isChartered = true
|
|
||||||
|
|
||||||
const created = await form.submitMain()
|
const created = await form.submitMain()
|
||||||
|
|
||||||
expect(created).toBe(false)
|
expect(created).toBe(false)
|
||||||
expect(form.mainErrors.errors.indexationRate).toBe("Le taux d'indexation est obligatoire pour un transporteur affrété.")
|
expect(form.mainErrors.errors.name).toBe('Le nom du transporteur ne peut dépasser 255 caractères.')
|
||||||
expect(form.mainLocked.value).toBe(false)
|
expect(form.mainLocked.value).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -95,9 +95,11 @@ export function useCarrierForm() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Validation FRONT du formulaire principal : seul le nom est requis côté front
|
* Validation FRONT du formulaire principal : seul le nom est requis côté front
|
||||||
* (RG-4.01) : nom requis, et certification requise hors cas LIOT (où elle est
|
* (ERP-101) : feedback immédiat sur tous les champs obligatoires (y compris
|
||||||
* masquée). Le back reste la couche autoritaire (ERP-101) — les RG conditionnelles
|
* conditionnels), alignés sur les RG du back (qui reste autoritaire) :
|
||||||
* (affrètement, décharge AUTRE) sont re-validées serveur et remontées en 422 inline.
|
* - RG-4.01 : nom requis ; certification requise hors cas LIOT (où tout est masqué) ;
|
||||||
|
* - RG-4.02 : décharge requise si certification AUTRE ;
|
||||||
|
* - RG-4.03 : indexation + contenant + volume requis si « Affréter ».
|
||||||
*/
|
*/
|
||||||
function validateMainFront(): boolean {
|
function validateMainFront(): boolean {
|
||||||
let valid = true
|
let valid = true
|
||||||
@@ -105,11 +107,40 @@ export function useCarrierForm() {
|
|||||||
mainErrors.setError('name', t('transport.carriers.form.errors.nameRequired'))
|
mainErrors.setError('name', t('transport.carriers.form.errors.nameRequired'))
|
||||||
valid = false
|
valid = false
|
||||||
}
|
}
|
||||||
// RG-4.01 : la certification est obligatoire SAUF en cas LIOT (champ masqué).
|
|
||||||
if (!isLiot.value && !main.certificationType) {
|
// Cas LIOT : seul le nom compte, les autres champs sont masqués (RG-4.01).
|
||||||
|
if (isLiot.value) {
|
||||||
|
return valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// RG-4.01 : certification obligatoire hors LIOT.
|
||||||
|
if (!main.certificationType) {
|
||||||
mainErrors.setError('certificationType', t('transport.carriers.form.errors.certificationRequired'))
|
mainErrors.setError('certificationType', t('transport.carriers.form.errors.certificationRequired'))
|
||||||
valid = false
|
valid = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RG-4.02 : décharge obligatoire si certification AUTRE.
|
||||||
|
if (main.certificationType === 'AUTRE' && !main.dischargeDocumentIri) {
|
||||||
|
mainErrors.setError('dischargeDocument', t('transport.carriers.form.errors.dischargeRequired'))
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// RG-4.03 : indexation / contenant / volume obligatoires si affrété.
|
||||||
|
if (main.isChartered) {
|
||||||
|
if (!main.indexationRate.trim()) {
|
||||||
|
mainErrors.setError('indexationRate', t('transport.carriers.form.errors.indexationRequired'))
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
if (!main.containerType) {
|
||||||
|
mainErrors.setError('containerType', t('transport.carriers.form.errors.containerTypeRequired'))
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
if (!main.volumeM3.trim()) {
|
||||||
|
mainErrors.setError('volumeM3', t('transport.carriers.form.errors.volumeRequired'))
|
||||||
|
valid = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return valid
|
return valid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user