From 76fb01c06378c6a104abd8954ea1fe0e140b98be Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 17 Jun 2026 17:40:05 +0200 Subject: [PATCH] =?UTF-8?q?feat(transport)=20:=20modif=20=E2=80=94=20ongle?= =?UTF-8?q?t=20Qualimat=20(actualisation)=20+=20certification=20=C3=A9dita?= =?UTF-8?q?ble=20(d=C3=A9liage=20Qualimat)=20(ERP-172)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CarrierQualimatTab.vue | 201 ++++++++++++++++++ .../__tests__/useCarrierForm.test.ts | 70 ++++++ .../transport/composables/useCarrierForm.ts | 23 +- .../transport/pages/carriers/[id]/edit.vue | 28 ++- .../modules/transport/pages/carriers/new.vue | 201 ++---------------- 5 files changed, 333 insertions(+), 190 deletions(-) create mode 100644 frontend/modules/transport/components/CarrierQualimatTab.vue diff --git a/frontend/modules/transport/components/CarrierQualimatTab.vue b/frontend/modules/transport/components/CarrierQualimatTab.vue new file mode 100644 index 0000000..a57436b --- /dev/null +++ b/frontend/modules/transport/components/CarrierQualimatTab.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/frontend/modules/transport/composables/__tests__/useCarrierForm.test.ts b/frontend/modules/transport/composables/__tests__/useCarrierForm.test.ts index 506b4ee..e738402 100644 --- a/frontend/modules/transport/composables/__tests__/useCarrierForm.test.ts +++ b/frontend/modules/transport/composables/__tests__/useCarrierForm.test.ts @@ -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') + }) +}) diff --git a/frontend/modules/transport/composables/useCarrierForm.ts b/frontend/modules/transport/composables/useCarrierForm.ts index 007ece8..994855c 100644 --- a/frontend/modules/transport/composables/useCarrierForm.ts +++ b/frontend/modules/transport/composables/useCarrierForm.ts @@ -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, diff --git a/frontend/modules/transport/pages/carriers/[id]/edit.vue b/frontend/modules/transport/pages/carriers/[id]/edit.vue index 4ef6328..711e699 100644 --- a/frontend/modules/transport/pages/carriers/[id]/edit.vue +++ b/frontend/modules/transport/pages/carriers/[id]/edit.vue @@ -41,7 +41,7 @@ :required="true" :readonly="certificationReadonly" :error="mainErrors.errors.certificationType" - @update:model-value="(v: string | number | null) => main.certificationType = v === null ? null : String(v)" + @update:model-value="(v: string | number | null) => setCertification(v === null ? null : String(v))" /> + + +