feat(transport) : modif — onglet Qualimat (actualisation) + certification éditable (déliage Qualimat) (ERP-172)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m14s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Has been cancelled

This commit is contained in:
2026-06-17 17:40:05 +02:00
parent e76bd1dd63
commit 76fb01c063
5 changed files with 333 additions and 190 deletions
@@ -975,3 +975,73 @@ describe('useCarrierForm — édition (ERP-170)', () => {
expect(form.main.name).toBe('TRANSPORTS ACME')
})
})
describe('useCarrierForm — modification : Qualimat + certification (ERP-172)', () => {
const QUALIMAT_ROW = {
'@id': '/api/qualimat_carriers/42',
id: '42',
name: 'TRANSPORTS QUALIMAT',
address: '1 rue du Port',
postalCode: '86000',
city: 'Poitiers',
validityDate: '2027-01-15',
status: 'VALIDE',
}
beforeEach(() => {
mockPost.mockReset()
mockPatch.mockReset()
})
it('setCertification : quitter QUALIMAT délie la FK qualimatCarrier', () => {
const form = useCarrierForm()
form.main.qualimatCarrierIri = '/api/qualimat_carriers/42'
form.main.certificationType = 'QUALIMAT'
form.setCertification('GMP_PLUS')
expect(form.main.certificationType).toBe('GMP_PLUS')
expect(form.main.qualimatCarrierIri).toBeNull()
})
it('certificationReadonly : éditable en modification même pour un QUALIMAT', () => {
const form = useCarrierForm()
form.prefillFrom({
'@id': '/api/carriers/7', id: 7, name: 'ACME', certificationType: 'QUALIMAT',
qualimatCarrier: { '@id': '/api/qualimat_carriers/42' },
})
expect(form.isQualimat.value).toBe(true)
expect(form.certificationReadonly.value).toBe(false)
})
it('buildMainPayload : en modification, délie le Qualimat (qualimatCarrier: null) sans lien', () => {
const form = useCarrierForm()
form.prefillFrom({
'@id': '/api/carriers/7', id: 7, name: 'ACME', certificationType: 'QUALIMAT',
qualimatCarrier: { '@id': '/api/qualimat_carriers/42' },
})
form.setCertification('GMP_PLUS')
expect(form.buildMainPayload()).toMatchObject({ certificationType: 'GMP_PLUS', qualimatCarrier: null })
})
it('applyQualimatSelection : en modification, conserve l\'adresse existante (PATCH nom/certif/FK)', async () => {
mockPatch.mockResolvedValueOnce({})
const form = useCarrierForm()
form.prefillFrom({
'@id': '/api/carriers/7', id: 7, name: 'OLD', certificationType: 'GMP_PLUS',
address: { '@id': '/api/carrier_addresses/3', id: 3, city: 'Poitiers', street: 'rue A' },
})
const addressBefore = { ...form.address.value }
const ok = await form.applyQualimatSelection(QUALIMAT_ROW)
expect(ok).toBe(true)
// Décision « conserver » (ERP-172) : l'adresse n'est pas réécrite en modification.
expect(form.address.value).toEqual(addressBefore)
// Nom + certification + FK actualisés via PATCH.
expect(form.main.name).toBe('TRANSPORTS QUALIMAT')
expect(form.main.certificationType).toBe('QUALIMAT')
expect(form.main.qualimatCarrierIri).toBe('/api/qualimat_carriers/42')
})
})
@@ -91,8 +91,10 @@ export function useCarrierForm() {
// Transporteur QUALIMAT : la FK est posée → certification figée à « QUALIMAT ».
const isQualimat = computed(() => main.qualimatCarrierIri !== null)
// Certification masquée en cas LIOT ; lecture seule si QUALIMAT (ou bloc verrouillé).
// En MODIFICATION (ERP-172) : éditable même pour un QUALIMAT (le métier doit pouvoir
// changer la certification) — la sortie de QUALIMAT délie le référentiel.
const showCertification = computed(() => !isLiot.value)
const certificationReadonly = computed(() => isQualimat.value || mainLocked.value)
const certificationReadonly = computed(() => (isQualimat.value && !editMode.value) || mainLocked.value)
// RG-4.03 : champs d'affrètement (indexation / contenant / volume) visibles et
// obligatoires si « Affréter » coché — masqués en cas LIOT.
const showCharteredFields = computed(() => main.isChartered && !isLiot.value)
@@ -211,6 +213,19 @@ export function useCarrierForm() {
}
}
/**
* Change la certification (sélecteur). Quitter « QUALIMAT » délie le référentiel
* (FK qualimatCarrier vidée — ERP-172) : un transporteur n'est QUALIMAT que tant
* que sa certification l'est. La FK null est propagée au back par buildMainPayload
* (en modification uniquement).
*/
function setCertification(value: string | null): void {
main.certificationType = value
if (value !== 'QUALIMAT') {
main.qualimatCarrierIri = null
}
}
/**
* Payload du POST principal (groupe `carrier:write:main`). `name` et
* `certificationType` sont omis s'ils sont vides afin que la 422 porte la
@@ -236,9 +251,14 @@ export function useCarrierForm() {
payload.certificationType = main.certificationType
}
// FK QUALIMAT (saisie assistée, § 2.5) envoyée si une ligne a été intégrée.
// En MODIFICATION, on délie explicitement (null) si plus de lien — ex: la
// certification a changé de QUALIMAT vers autre chose (ERP-172).
if (main.qualimatCarrierIri) {
payload.qualimatCarrier = main.qualimatCarrierIri
}
else if (editMode.value) {
payload.qualimatCarrier = null
}
// RG-4.02 : décharge envoyée seulement en certification AUTRE ; omise quand
// absente pour que la 422 « obligatoire » porte sur le champ.
if (main.certificationType === 'AUTRE' && main.dischargeDocumentIri) {
@@ -799,6 +819,7 @@ export function useCarrierForm() {
removePrice,
submitPrices,
// actions
setCertification,
selectDischarge,
clearDischarge,
validateMainFront,