fix(transport) : onglet Contact transporteur non obligatoire + navigation onglets (ERP-193)
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 46s
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m24s

- retrait de la regle « prenom OU nom » sur le bloc Contact : garde
  CarrierContactProcessor::validateName supprimee, CHECK chk_carrier_contact_name
  droppe (migration Version20260619120000), commentaires SQL/catalogue alignes
- front : gating « + Nouveau contact » sur bloc non vide (au lieu de « nomme »),
  onglet Contact vide finalisable sans creer de contact
- Prix accessible des la validation des Adresses (Contacts optionnel ne bloque plus)
- consultation <-> edition : on retombe sur le meme onglet via ?tab=
This commit is contained in:
2026-06-19 14:53:52 +02:00
parent c11d7822ce
commit 833d992ebb
12 changed files with 131 additions and 124 deletions
@@ -17,7 +17,7 @@ import {
type CarrierPriceFormDraft,
} from '~/modules/transport/types/carrierForm'
import { buildCarrierAddressPayload } from '~/modules/transport/utils/forms/carrierAddress'
import { buildCarrierContactPayload, isCarrierContactBlank, isCarrierContactNamed } from '~/modules/transport/utils/forms/carrierContact'
import { buildCarrierContactPayload, isCarrierContactBlank } from '~/modules/transport/utils/forms/carrierContact'
import { buildCarrierPricePayload, isCarrierPriceValid } from '~/modules/transport/utils/forms/carrierPrice'
import {
mapAddressToDraft,
@@ -488,6 +488,10 @@ export function useCarrierForm() {
await api.patch(`/carrier_addresses/${address.value.id}`, body, { toast: false })
}
completeTab('addresses')
// ERP-193 : l'onglet Contact est OPTIONNEL — il ne doit pas verrouiller
// l'accès à Prix. Dès les Adresses validées, on déverrouille jusqu'à Prix
// (Contacts reste accessible mais n'est plus une étape bloquante).
unlockedIndex.value = tabKeys.value.length - 1
return true
}
catch (error) {
@@ -511,12 +515,13 @@ export function useCarrierForm() {
// Erreurs 422 par ligne (alignées sur l'index du v-for), peuplées par submitRows.
const contactErrors = ref<Record<string, string>[]>([])
// « + 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).
// « + Nouveau contact » désactivé tant que le DERNIER bloc est VIDE. ERP-193 :
// l'onglet Contact n'est plus obligatoire — on ne réclame plus prénom OU nom,
// un seul champ rempli (fonction / téléphone / email) suffit pour empiler un
// bloc suivant (et évite d'accumuler des blocs totalement vides).
const canAddContact = computed(() => {
const last = contacts.value[contacts.value.length - 1]
return last !== undefined && isCarrierContactNamed(last)
return last !== undefined && !isCarrierContactBlank(last)
})
function addContact(): void {
@@ -541,10 +546,11 @@ export function useCarrierForm() {
/**
* Valide l'onglet Contacts : POST des nouveaux contacts sur
* /carriers/{id}/contacts, PATCH des existants sur /carrier_contacts/{id}
* (groupe carrier:write:contacts). RG-4.08 (≥ 1 champ rempli, max 2 téléphones)
* re-validée back → 422 par ligne. Si l'onglet ne contient QUE des amorces
* vides, on soumet la 1re pour déclencher la 422 RG-4.08 inline plutôt que de
* finaliser un onglet vide. Retourne true si l'onglet a été validé.
* (groupe carrier:write:contacts). Max 2 téléphones re-validé back → 422 par
* ligne. ERP-193 : l'onglet Contact est OPTIONNEL — les amorces vides neuves
* sont systématiquement ignorées (pas de contact vide créé) et un onglet sans
* aucun bloc rempli est simplement finalisé, déverrouillant l'onglet Prix.
* Retourne true si l'onglet a été validé.
*/
async function submitContacts(onError: (error: unknown) => void): Promise<boolean> {
if (carrierId.value === null || tabSubmitting.value) {
@@ -552,7 +558,6 @@ export function useCarrierForm() {
}
tabSubmitting.value = true
try {
const hasSubmittable = contacts.value.some(c => c.id !== null || !isCarrierContactBlank(c))
const hasError = await submitRows(
contacts.value,
contactErrors,
@@ -571,9 +576,9 @@ export function useCarrierForm() {
}
},
onError,
// Amorce vide neuve ignorée s'il reste un autre bloc soumettable ;
// sinon on la soumet pour déclencher la 422 RG-4.08 (sur firstName).
contact => hasSubmittable && contact.id === null && isCarrierContactBlank(contact),
// Amorce vide neuve toujours ignorée (bloc Contact optionnel, ERP-193) :
// un onglet sans aucun bloc rempli se finalise sans rien créer.
contact => contact.id === null && isCarrierContactBlank(contact),
)
if (hasError) {
return false