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`.