fix(transport) : règle « + Nouveau contact » alignée sur M1/M2/M3 (prénom OU nom) (ERP-168)
This commit is contained in:
@@ -37,7 +37,7 @@ vi.stubGlobal('useToast', () => ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
const { useCarrierForm, CARRIER_TAB_KEYS } = await import('../useCarrierForm')
|
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')
|
const { emptyCarrierContact } = await import('../../types/carrierForm')
|
||||||
|
|
||||||
describe('useCarrierForm', () => {
|
describe('useCarrierForm', () => {
|
||||||
@@ -541,11 +541,22 @@ describe('useCarrierForm — onglet Adresses (ERP-167)', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('carrierContact (util) — RG-4.08 + max 2 téléphones', () => {
|
describe('carrierContact (util) — validité alignée M1/M2/M3 + max 2 téléphones', () => {
|
||||||
it('isCarrierContactBlank : vrai si aucun champ, faux dès un champ rempli', () => {
|
it('isCarrierContactBlank : vrai si vide, faux dès un champ comptant rempli (phoneSecondary exclu)', () => {
|
||||||
expect(isCarrierContactBlank(emptyCarrierContact())).toBe(true)
|
expect(isCarrierContactBlank(emptyCarrierContact())).toBe(true)
|
||||||
expect(isCarrierContactBlank({ ...emptyCarrierContact(), jobTitle: 'Acheteur' })).toBe(false)
|
expect(isCarrierContactBlank({ ...emptyCarrierContact(), jobTitle: 'Acheteur' })).toBe(false)
|
||||||
expect(isCarrierContactBlank({ ...emptyCarrierContact(), phonePrimary: '0102030405' })).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', () => {
|
it('buildCarrierContactPayload : phones = 1 numéro sans secondaire', () => {
|
||||||
@@ -588,15 +599,22 @@ describe('useCarrierForm — onglet Contacts (ERP-168)', () => {
|
|||||||
return form
|
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()
|
const form = createdForm()
|
||||||
expect(form.canAddContact.value).toBe(false)
|
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()
|
form.addContact()
|
||||||
expect(form.contacts.value).toHaveLength(1)
|
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]
|
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'
|
if (first) first.lastName = 'Doe'
|
||||||
expect(form.canAddContact.value).toBe(true)
|
expect(form.canAddContact.value).toBe(true)
|
||||||
form.addContact()
|
form.addContact()
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
type CarrierMainResponse,
|
type CarrierMainResponse,
|
||||||
} from '~/modules/transport/types/carrierForm'
|
} from '~/modules/transport/types/carrierForm'
|
||||||
import { buildCarrierAddressPayload, isCarrierAddressValid } from '~/modules/transport/utils/forms/carrierAddress'
|
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'
|
import type { QualimatCarrierRow } from '~/modules/transport/composables/useQualimatSearch'
|
||||||
|
|
||||||
/** Nom du cas spécial « compte-propre » LIOT (comparaison insensible à la casse, RG-4.01). */
|
/** 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.
|
// Erreurs 422 par ligne (alignées sur l'index du v-for), peuplées par submitRows.
|
||||||
const contactErrors = ref<Record<string, string>[]>([])
|
const contactErrors = ref<Record<string, string>[]>([])
|
||||||
|
|
||||||
// RG-4.08 : « + Nouveau contact » désactivé tant que le DERNIER bloc est vide
|
// « + Nouveau contact » désactivé tant que le DERNIER bloc n'est pas « nommé »
|
||||||
// (aucun champ rempli).
|
// (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 canAddContact = computed(() => {
|
||||||
const last = contacts.value[contacts.value.length - 1]
|
const last = contacts.value[contacts.value.length - 1]
|
||||||
return last !== undefined && !isCarrierContactBlank(last)
|
return last !== undefined && isCarrierContactNamed(last)
|
||||||
})
|
})
|
||||||
|
|
||||||
function addContact(): void {
|
function addContact(): void {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
/**
|
/**
|
||||||
* Helpers purs de l'onglet Contact transporteur (M4 Transport, ERP-168) — miroir
|
* Helpers purs de l'onglet Contact transporteur (M4 Transport, ERP-168) — ALIGNÉ
|
||||||
* de `providerContact.ts` (M3), avec deux spécificités M4 :
|
* sur `providerContact.ts` (M3) / les autres modules : mêmes règles de validité et
|
||||||
* - RG-4.08 : un bloc est valide dès qu'AU MOINS UN champ est rempli (n'importe
|
* de gating « + Nouveau contact » (un contact est « nommé » dès qu'il porte un
|
||||||
* lequel) — ≠ M3 qui n'exigeait que le nom.
|
* prénom OU un nom). Seule spécificité M4 conservée : les téléphones partent au back
|
||||||
* - les téléphones partent au back dans le tableau virtuel `phones` (max 2),
|
* dans le tableau virtuel `phones` (max 2), mappés par le CarrierContactProcessor.
|
||||||
* pas en `phonePrimary` / `phoneSecondary` (mappés par le CarrierContactProcessor).
|
|
||||||
* Testables sans Vue ni API.
|
* 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
|
* Un bloc Contact est VIDE tant qu'aucun champ comptant pour la validité n'est
|
||||||
* (prénom / nom / fonction / téléphone(s) / email). Sert le gating « + Nouveau
|
* rempli — prénom / nom / fonction / téléphone principal / email. `phoneSecondary`
|
||||||
* contact » (on n'ajoute pas de bloc tant que le précédent est vide) et reflète la
|
* est EXCLU (aligné M1/M2/M3 : un bloc ne portant qu'un 2e numéro reste vide). Sert
|
||||||
* garde back (CarrierContactProcessor + CHECK chk_carrier_contact_filled).
|
* le filtrage des amorces vides à la soumission.
|
||||||
*/
|
*/
|
||||||
export function isCarrierContactBlank(contact: CarrierContactFormDraft): boolean {
|
export function isCarrierContactBlank(contact: CarrierContactFormDraft): boolean {
|
||||||
return ![
|
return ![
|
||||||
@@ -27,11 +26,19 @@ export function isCarrierContactBlank(contact: CarrierContactFormDraft): boolean
|
|||||||
contact.lastName,
|
contact.lastName,
|
||||||
contact.jobTitle,
|
contact.jobTitle,
|
||||||
contact.phonePrimary,
|
contact.phonePrimary,
|
||||||
contact.phoneSecondary,
|
|
||||||
contact.email,
|
contact.email,
|
||||||
].some(isFilled)
|
].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
|
* 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
|
* chaînes vides partent à null (le serveur normalise/trim). Les téléphones sont
|
||||||
|
|||||||
Reference in New Issue
Block a user