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
@@ -21,9 +21,9 @@ use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Contact d'un transporteur (1:n) — onglet Contact (M4). Jumeau de
* SupplierContact (M2) : au moins le prenom OU le nom (RG-4.08, garanti par le
* CHECK chk_carrier_contact_name + le Processor), max 2 telephones.
* Contact d'un transporteur (1:n) — onglet Contact (M4). ERP-193 (retour metier) :
* le bloc Contact est OPTIONNEL — la garde « prenom OU nom » (ex RG-4.08) est
* retiree. Reste applicable : max 2 telephones.
*
* Lecture : proprietes en `carrier:item:read` (embarquees au detail du
* transporteur). Ecriture : groupe `carrier:write:contacts`.
@@ -23,23 +23,19 @@ use function is_string;
/**
* Processor d'ecriture de la sous-ressource Contact d'un transporteur (M4,
* spec-back § 4.5). Jumeau du SupplierContactProcessor (M2), recentre sur le
* perimetre ERP-160. RG-4.08 (correctif, alignement M1/M2/M3) : un contact exige
* au moins le PRENOM OU le NOM (la fonction / le telephone / l'email seuls ne
* suffisent pas), porte a la fois par le CHECK BDD chk_carrier_contact_name et par
* ce Processor ; le « max 2 telephones » reste une specificite M4.
* perimetre ERP-160. ERP-193 (retour metier) : l'onglet Contact n'est plus
* obligatoire — la garde « prenom OU nom » (ex RG-4.08) est retiree, un contact
* peut donc etre cree sans nom. Le « max 2 telephones » reste une specificite M4.
*
* Sequence :
* - POST / PATCH : rattachement au transporteur parent (linkParent),
* normalisation serveur RG-4.13 (prenom/nom Title Case, email lowercase),
* mapping du tableau d'ecriture `phones` -> phonePrimary/phoneSecondary
* (max 2, chiffres uniquement), puis garde « prenom OU nom » avant persistance.
* (max 2, chiffres uniquement) avant persistance.
* - DELETE : aucune regle metier specifique (suppression physique directe).
*
* La garde « prenom OU nom » vit ICI (double du CHECK BDD) pour transformer une
* violation SQL (500 generique) en 422 propre rattachee au champ `firstName`
* (mapping inline ERP-101). Le « max 2 telephones » est rattache au champ `phones` : seul
* point de saisie des numeros (les colonnes phonePrimary/phoneSecondary sont en
* lecture seule).
* Le « max 2 telephones » est rattache au champ `phones` : seul point de saisie
* des numeros (les colonnes phonePrimary/phoneSecondary sont en lecture seule).
*
* La security d'operation (transport.carriers.manage) est appliquee par API
* Platform en amont, de meme que la validation Symfony des contraintes d'attribut
@@ -77,7 +73,6 @@ final class CarrierContactProcessor implements ProcessorInterface
$this->linkParent($data, $uriVariables);
$this->normalize($data);
$this->applyPhones($data);
$this->validateName($data);
return $this->persistProcessor->process($data, $operation, $uriVariables, $context);
}
@@ -115,9 +110,8 @@ final class CarrierContactProcessor implements ProcessorInterface
/**
* Normalisation serveur RG-4.13 des champs texte. Toutes les methodes du
* normalizer sont null-safe : une chaine vide apres trim devient null (donc la
* garde RG-4.08 detecte bien « champ non rempli »). Les telephones sont
* traites a part (applyPhones).
* normalizer sont null-safe : une chaine vide apres trim devient null. Les
* telephones sont traites a part (applyPhones).
*/
private function normalize(CarrierContact $contact): void
{
@@ -186,30 +180,6 @@ final class CarrierContactProcessor implements ProcessorInterface
$contact->setPhones(null);
}
/**
* RG-4.08 (alignement M1/M2/M3) : un bloc Contact exige au moins le PRENOM OU le
* NOM — un contact se materialise par son nom ; fonction / telephone / email
* seuls ne suffisent pas. Double garde avec le CHECK BDD chk_carrier_contact_name
* — leve une 422 propre rattachee a `firstName` plutot qu'une 500 SQL. Joue apres
* normalisation + mapping telephones, donc les chaines vides sont deja null.
*/
private function validateName(CarrierContact $contact): void
{
if (null === $contact->getFirstName() && null === $contact->getLastName()) {
$violations = new ConstraintViolationList();
$violations->add(new ConstraintViolation(
'Le prénom ou le nom du contact est obligatoire.',
null,
[],
$contact,
'firstName',
null,
));
throw new ValidationException($violations);
}
}
/**
* Trim + chaine vide -> null (la fonction n'est pas normalisee en casse,
* contrairement aux noms de personne). Evite de persister une chaine vide
@@ -195,7 +195,8 @@ class CarrierFixtures extends Fixture implements DependentFixtureInterface
/**
* Ajoute un contact normalise au transporteur (cascade persist via
* Carrier.contacts). Prenom OU nom toujours fourni (RG-4.08, chk_carrier_contact_name).
* Carrier.contacts). Le bloc Contact est optionnel (ERP-193) ; les fixtures
* fournissent neanmoins un nom pour des donnees de demonstration realistes.
*/
private function addContact(
Carrier $carrier,
@@ -508,11 +508,11 @@ final class ColumnCommentsCatalog
] + self::timestampableBlamableComments(),
'carrier_contact' => [
'_table' => 'Contacts d un transporteur (1:n) — onglet Contact (M4). Au moins le prenom OU le nom rempli (RG-4.08, chk_carrier_contact_name), max 2 telephones.',
'_table' => 'Contacts d un transporteur (1:n) — onglet Contact (M4). Bloc optionnel (ERP-193) ; max 2 telephones.',
'id' => 'Identifiant interne auto-incremente.',
'carrier_id' => 'FK -> carrier.id, ON DELETE CASCADE — transporteur proprietaire du contact.',
'first_name' => 'Prenom du contact (capitalise serveur). Prenom OU nom obligatoire (RG-4.08, chk_carrier_contact_name).',
'last_name' => 'Nom du contact (capitalise serveur). Prenom OU nom obligatoire (RG-4.08, chk_carrier_contact_name).',
'first_name' => 'Prenom du contact (capitalise serveur). Optionnel (ERP-193).',
'last_name' => 'Nom du contact (capitalise serveur). Optionnel (ERP-193).',
'job_title' => 'Fonction / intitule de poste du contact (≤ 120 caracteres).',
'phone_primary' => 'Telephone principal — chiffres uniquement (normalisation serveur).',
'phone_secondary' => 'Telephone secondaire — chiffres uniquement (max 2 telephones, RG-4.08).',