From 074904ffcefc89f6fc82adcdb968501b84a0362f Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 17 Jun 2026 09:34:38 +0200 Subject: [PATCH] =?UTF-8?q?fix(transport)=20:=20r=C3=A8gle=20=C2=AB=20+=20?= =?UTF-8?q?Nouveau=20contact=20=C2=BB=20align=C3=A9e=20sur=20M1/M2/M3=20(p?= =?UTF-8?q?r=C3=A9nom=20OU=20nom)=20(ERP-168)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/useCarrierForm.test.ts | 28 ++++++++++++++---- .../transport/composables/useCarrierForm.ts | 9 +++--- .../transport/utils/forms/carrierContact.ts | 29 ++++++++++++------- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/frontend/modules/transport/composables/__tests__/useCarrierForm.test.ts b/frontend/modules/transport/composables/__tests__/useCarrierForm.test.ts index e28a5e9..a079b74 100644 --- a/frontend/modules/transport/composables/__tests__/useCarrierForm.test.ts +++ b/frontend/modules/transport/composables/__tests__/useCarrierForm.test.ts @@ -37,7 +37,7 @@ vi.stubGlobal('useToast', () => ({ })) const { useCarrierForm, CARRIER_TAB_KEYS } = await import('../useCarrierForm') -const { buildCarrierContactPayload, isCarrierContactBlank } = await import('../../utils/forms/carrierContact') +const { buildCarrierContactPayload, isCarrierContactBlank, isCarrierContactNamed } = await import('../../utils/forms/carrierContact') const { emptyCarrierContact } = await import('../../types/carrierForm') describe('useCarrierForm', () => { @@ -541,11 +541,22 @@ describe('useCarrierForm — onglet Adresses (ERP-167)', () => { }) }) -describe('carrierContact (util) — RG-4.08 + max 2 téléphones', () => { - it('isCarrierContactBlank : vrai si aucun champ, faux dès un champ rempli', () => { +describe('carrierContact (util) — validité alignée M1/M2/M3 + max 2 téléphones', () => { + it('isCarrierContactBlank : vrai si vide, faux dès un champ comptant rempli (phoneSecondary exclu)', () => { expect(isCarrierContactBlank(emptyCarrierContact())).toBe(true) expect(isCarrierContactBlank({ ...emptyCarrierContact(), jobTitle: 'Acheteur' })).toBe(false) expect(isCarrierContactBlank({ ...emptyCarrierContact(), phonePrimary: '0102030405' })).toBe(false) + // phoneSecondary seul ne compte pas (aligné M1/M2/M3). + expect(isCarrierContactBlank({ ...emptyCarrierContact(), phoneSecondary: '0605040302', hasSecondaryPhone: true })).toBe(true) + }) + + it('isCarrierContactNamed : nommé seulement avec un prénom OU un nom', () => { + expect(isCarrierContactNamed(emptyCarrierContact())).toBe(false) + expect(isCarrierContactNamed({ ...emptyCarrierContact(), firstName: 'Jean' })).toBe(true) + expect(isCarrierContactNamed({ ...emptyCarrierContact(), lastName: 'Doe' })).toBe(true) + // Fonction / téléphone / email seuls ne « nomment » pas (≠ RG-4.08 large). + expect(isCarrierContactNamed({ ...emptyCarrierContact(), jobTitle: 'Acheteur' })).toBe(false) + expect(isCarrierContactNamed({ ...emptyCarrierContact(), email: 'a@b.fr' })).toBe(false) }) it('buildCarrierContactPayload : phones = 1 numéro sans secondaire', () => { @@ -588,15 +599,22 @@ describe('useCarrierForm — onglet Contacts (ERP-168)', () => { return form } - it('RG-4.08 : « + Nouveau contact » désactivé tant que le bloc est vide', () => { + it('« + Nouveau contact » désactivé tant que le bloc n\'est pas nommé (prénom OU nom, aligné M1/M2/M3)', () => { const form = createdForm() expect(form.canAddContact.value).toBe(false) - // addContact est un no-op tant que le bloc est vide. + // addContact est un no-op tant que le bloc n'est pas nommé. form.addContact() expect(form.contacts.value).toHaveLength(1) + // Fonction seule ne suffit PAS à ajouter un nouveau bloc (≠ RG-4.08 large). const first = form.contacts.value[0] + if (first) first.jobTitle = 'Acheteur' + expect(form.canAddContact.value).toBe(false) + form.addContact() + expect(form.contacts.value).toHaveLength(1) + + // Un nom (ou prénom) débloque l'ajout. if (first) first.lastName = 'Doe' expect(form.canAddContact.value).toBe(true) form.addContact() diff --git a/frontend/modules/transport/composables/useCarrierForm.ts b/frontend/modules/transport/composables/useCarrierForm.ts index 7899ea4..ebacad6 100644 --- a/frontend/modules/transport/composables/useCarrierForm.ts +++ b/frontend/modules/transport/composables/useCarrierForm.ts @@ -14,7 +14,7 @@ import { type CarrierMainResponse, } from '~/modules/transport/types/carrierForm' import { buildCarrierAddressPayload, isCarrierAddressValid } from '~/modules/transport/utils/forms/carrierAddress' -import { buildCarrierContactPayload, isCarrierContactBlank } from '~/modules/transport/utils/forms/carrierContact' +import { buildCarrierContactPayload, isCarrierContactBlank, isCarrierContactNamed } from '~/modules/transport/utils/forms/carrierContact' import type { QualimatCarrierRow } from '~/modules/transport/composables/useQualimatSearch' /** Nom du cas spécial « compte-propre » LIOT (comparaison insensible à la casse, RG-4.01). */ @@ -390,11 +390,12 @@ export function useCarrierForm() { // Erreurs 422 par ligne (alignées sur l'index du v-for), peuplées par submitRows. const contactErrors = ref[]>([]) - // RG-4.08 : « + Nouveau contact » désactivé tant que le DERNIER bloc est vide - // (aucun champ rempli). + // « + Nouveau contact » désactivé tant que le DERNIER bloc n'est pas « nommé » + // (prénom OU nom) — aligné sur M1/M2/M3 (fonction / téléphone / email seuls ne + // suffisent pas à ajouter un nouveau bloc). const canAddContact = computed(() => { const last = contacts.value[contacts.value.length - 1] - return last !== undefined && !isCarrierContactBlank(last) + return last !== undefined && isCarrierContactNamed(last) }) function addContact(): void { diff --git a/frontend/modules/transport/utils/forms/carrierContact.ts b/frontend/modules/transport/utils/forms/carrierContact.ts index c006367..3d434f4 100644 --- a/frontend/modules/transport/utils/forms/carrierContact.ts +++ b/frontend/modules/transport/utils/forms/carrierContact.ts @@ -1,10 +1,9 @@ /** - * Helpers purs de l'onglet Contact transporteur (M4 Transport, ERP-168) — miroir - * de `providerContact.ts` (M3), avec deux spécificités M4 : - * - RG-4.08 : un bloc est valide dès qu'AU MOINS UN champ est rempli (n'importe - * lequel) — ≠ M3 qui n'exigeait que le nom. - * - les téléphones partent au back dans le tableau virtuel `phones` (max 2), - * pas en `phonePrimary` / `phoneSecondary` (mappés par le CarrierContactProcessor). + * Helpers purs de l'onglet Contact transporteur (M4 Transport, ERP-168) — ALIGNÉ + * sur `providerContact.ts` (M3) / les autres modules : mêmes règles de validité et + * de gating « + Nouveau contact » (un contact est « nommé » dès qu'il porte un + * prénom OU un nom). Seule spécificité M4 conservée : les téléphones partent au back + * dans le tableau virtuel `phones` (max 2), mappés par le CarrierContactProcessor. * Testables sans Vue ni API. */ @@ -16,10 +15,10 @@ function isFilled(value: string | null | undefined): boolean { } /** - * RG-4.08 : un bloc Contact est VIDE tant qu'aucun de ses champs n'est rempli - * (prénom / nom / fonction / téléphone(s) / email). Sert le gating « + Nouveau - * contact » (on n'ajoute pas de bloc tant que le précédent est vide) et reflète la - * garde back (CarrierContactProcessor + CHECK chk_carrier_contact_filled). + * Un bloc Contact est VIDE tant qu'aucun champ comptant pour la validité n'est + * rempli — prénom / nom / fonction / téléphone principal / email. `phoneSecondary` + * est EXCLU (aligné M1/M2/M3 : un bloc ne portant qu'un 2e numéro reste vide). Sert + * le filtrage des amorces vides à la soumission. */ export function isCarrierContactBlank(contact: CarrierContactFormDraft): boolean { return ![ @@ -27,11 +26,19 @@ export function isCarrierContactBlank(contact: CarrierContactFormDraft): boolean contact.lastName, contact.jobTitle, contact.phonePrimary, - contact.phoneSecondary, contact.email, ].some(isFilled) } +/** + * Un contact est « nommé » (valide) dès qu'il porte un prénom OU un nom — aligné + * sur M1/M2/M3. Pilote le gating « + Nouveau contact » : la fonction / le téléphone + * / l'email seuls ne suffisent pas pour ajouter un nouveau bloc. + */ +export function isCarrierContactNamed(contact: CarrierContactFormDraft): boolean { + return isFilled(contact.firstName) || isFilled(contact.lastName) +} + /** * Payload de la sous-ressource contacts (groupe `carrier:write:contacts`). Les * chaînes vides partent à null (le serveur normalise/trim). Les téléphones sont