From e6188535fd3cb23c9c0d19da9006bec45f431015 Mon Sep 17 00:00:00 2001 From: Matthieu Date: Tue, 2 Jun 2026 00:10:43 +0200 Subject: [PATCH] fix(commercial) : billingEmail vide rejete en 422 et non en 500 (RG-1.11) Le callback validateBillingEmailPresence testait null === billingEmail. Une chaine vide "" (champ vide envoye par le client) passait donc les validators ; le ClientAddressProcessor la normalisait ensuite en null APRES la validation, puis la persistance d'une adresse is_billing=true avec billing_email=null violait le CHECK chk_client_address_billing_email -> 500 DBAL au lieu du 422 attendu. Symetriquement, "" sur une adresse non facturable etait rejete a tort en 422 alors qu'un champ vide equivaut a une absence d'email. Le callback raisonne desormais sur la presence effective (null OU chaine vide apres trim = absent), coheremment avec la normalisation du processor. Deux cas de test ajoutes : adresse facturable + "" -> 422, adresse non facturable + "" -> 201. --- .../Domain/Entity/ClientAddress.php | 14 ++++- .../Commercial/Api/ClientAddressTest.php | 54 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/Module/Commercial/Domain/Entity/ClientAddress.php b/src/Module/Commercial/Domain/Entity/ClientAddress.php index 615d5ae..148a0ac 100644 --- a/src/Module/Commercial/Domain/Entity/ClientAddress.php +++ b/src/Module/Commercial/Domain/Entity/ClientAddress.php @@ -202,18 +202,28 @@ class ClientAddress implements TimestampableInterface, BlamableInterface * RG-1.11 : l'email de facturation est obligatoire si l'adresse est de * facturation, et interdit sinon. Mirror applicatif (422) du CHECK * chk_client_address_billing_email. + * + * On raisonne sur la PRESENCE effective de l'email : null ET chaine vide + * sont traites comme « absent », car le ClientAddressProcessor normalise une + * chaine vide en null APRES la validation (RG-1.21). Sans ce traitement, + * billingEmail="" passerait les callbacks (null === "" est faux) puis serait + * persiste en null avec is_billing=true -> violation du CHECK -> 500 au lieu + * du 422 attendu (et symetriquement, "" sur une adresse non facturable + * serait rejete a tort). */ #[Assert\Callback] public function validateBillingEmailPresence(ExecutionContextInterface $context): void { - if ($this->isBilling && null === $this->billingEmail) { + $hasBillingEmail = null !== $this->billingEmail && '' !== trim($this->billingEmail); + + if ($this->isBilling && !$hasBillingEmail) { $context->buildViolation('L\'email de facturation est obligatoire pour une adresse de facturation.') ->atPath('billingEmail') ->addViolation() ; } - if (!$this->isBilling && null !== $this->billingEmail) { + if (!$this->isBilling && $hasBillingEmail) { $context->buildViolation('L\'email de facturation n\'est autorisé que sur une adresse de facturation.') ->atPath('billingEmail') ->addViolation() diff --git a/tests/Module/Commercial/Api/ClientAddressTest.php b/tests/Module/Commercial/Api/ClientAddressTest.php index 621170a..11b6be0 100644 --- a/tests/Module/Commercial/Api/ClientAddressTest.php +++ b/tests/Module/Commercial/Api/ClientAddressTest.php @@ -106,6 +106,33 @@ final class ClientAddressTest extends AbstractCommercialApiTestCase self::assertResponseStatusCodeSame(422); } + /** + * RG-1.11 (cas chaine vide) : une adresse de facturation avec un billingEmail + * vide ("") doit etre rejetee en 422, et NON passer la validation pour finir + * en 500 sur le CHECK BDD. Le ClientAddressProcessor normalise "" -> null + * APRES la validation : le callback doit donc traiter "" comme « absent ». + */ + public function testBillingAddressRejectsEmptyBillingEmail(): void + { + $this->skipIfSitesModuleDisabled(); + $client = $this->createAdminClient(); + $seed = $this->seedClient('Billing Empty Email'); + + $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [ + 'headers' => ['Content-Type' => self::LD], + 'json' => [ + 'isBilling' => true, + 'billingEmail' => '', + 'postalCode' => '86100', + 'city' => 'Châtellerault', + 'street' => '1 rue du Test', + 'sites' => [$this->firstSiteIri()], + ], + ]); + + self::assertResponseStatusCodeSame(422); + } + /** * RG-1.11 (sens inverse) : une adresse NON facturable ne peut pas porter un * billingEmail -> 422. @@ -131,6 +158,33 @@ final class ClientAddressTest extends AbstractCommercialApiTestCase self::assertResponseStatusCodeSame(422); } + /** + * RG-1.11 (sens inverse, cas chaine vide) : une adresse NON facturable avec + * un billingEmail vide ("") est ACCEPTEE (201). Le "" equivaut a « pas + * d'email » : il ne doit pas declencher la violation « email interdit hors + * facturation » (sinon un champ simplement vide serait refuse a tort). + */ + public function testNonBillingAddressAcceptsEmptyBillingEmail(): void + { + $this->skipIfSitesModuleDisabled(); + $client = $this->createAdminClient(); + $seed = $this->seedClient('Non Billing Empty Email'); + + $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [ + 'headers' => ['Content-Type' => self::LD], + 'json' => [ + 'isBilling' => false, + 'billingEmail' => '', + 'postalCode' => '86100', + 'city' => 'Châtellerault', + 'street' => '1 rue du Test', + 'sites' => [$this->firstSiteIri()], + ], + ]); + + self::assertResponseStatusCodeSame(201); + } + /** * RG-1.29 : poster une categorie de type DISTRIBUTEUR sur une adresse -> 422 * avec violation sur le champ `categories`.