From 7a45d1772464f0eae0bea2fc121b3a8fe00db4d5 Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 3 Jun 2026 15:57:26 +0200 Subject: [PATCH] =?UTF-8?q?fix(back,front)=20:=20adresse=20client=20?= =?UTF-8?q?=E2=80=94=20au=20moins=20une=20categorie=20obligatoire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spec-front § Adresse : la categorie est obligatoire sur une adresse, mais n'etait enforced ni au back ni au front. - Back : ClientAddress::$categories porte desormais Assert\Count(min: 1) (POST/PATCH sans categorie -> 422). Test testAddressRequiresAtLeastOneCategory ; deux tests existants qui creaient une adresse sans categorie recoivent une categorie SECTEUR. - Front : canValidateAddresses (creation + modification) exige >= 1 categorie par adresse -> bouton Enregistrer desactive tant qu'aucune categorie n'est choisie (meme gating que les sites). --- .../commercial/pages/clients/[id]/edit.vue | 4 ++- .../modules/commercial/pages/clients/new.vue | 4 ++- .../Domain/Entity/ClientAddress.php | 2 ++ .../Commercial/Api/ClientAddressTest.php | 29 +++++++++++++++++-- .../Api/ClientSubResourceApiTest.php | 8 +++-- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/frontend/modules/commercial/pages/clients/[id]/edit.vue b/frontend/modules/commercial/pages/clients/[id]/edit.vue index d1614ec..06a2f77 100644 --- a/frontend/modules/commercial/pages/clients/[id]/edit.vue +++ b/frontend/modules/commercial/pages/clients/[id]/edit.vue @@ -743,7 +743,9 @@ const canValidateAddresses = computed(() => addresses.value.length > 0 && addresses.value.every((a) => { const filledBillingEmail = a.billingEmail !== null && a.billingEmail.trim() !== '' - return a.siteIris.length >= 1 && (!isBillingEmailRequired(a) || filledBillingEmail) + return a.siteIris.length >= 1 + && a.categoryIris.length >= 1 + && (!isBillingEmailRequired(a) || filledBillingEmail) }), ) diff --git a/frontend/modules/commercial/pages/clients/new.vue b/frontend/modules/commercial/pages/clients/new.vue index 8502eec..7bbdbe6 100644 --- a/frontend/modules/commercial/pages/clients/new.vue +++ b/frontend/modules/commercial/pages/clients/new.vue @@ -755,7 +755,9 @@ const canValidateAddresses = computed(() => addresses.value.length > 0 && addresses.value.every((a) => { const filledBillingEmail = a.billingEmail !== null && a.billingEmail.trim() !== '' - return a.siteIris.length >= 1 && (!isBillingEmailRequired(a) || filledBillingEmail) + return a.siteIris.length >= 1 + && a.categoryIris.length >= 1 + && (!isBillingEmailRequired(a) || filledBillingEmail) }), ) diff --git a/src/Module/Commercial/Domain/Entity/ClientAddress.php b/src/Module/Commercial/Domain/Entity/ClientAddress.php index c359088..25d779d 100644 --- a/src/Module/Commercial/Domain/Entity/ClientAddress.php +++ b/src/Module/Commercial/Domain/Entity/ClientAddress.php @@ -177,12 +177,14 @@ class ClientAddress implements TimestampableInterface, BlamableInterface #[Groups(['client_address:read', 'client_address:write'])] private Collection $contacts; + // Au moins une categorie est obligatoire sur une adresse (spec-front § Adresse). // RG-1.29 : categories de code DISTRIBUTEUR/COURTIER interdites (validateCategoryCodes). /** @var Collection */ #[ORM\ManyToMany(targetEntity: CategoryInterface::class)] #[ORM\JoinTable(name: 'client_address_category')] #[ORM\JoinColumn(name: 'client_address_id', referencedColumnName: 'id', onDelete: 'CASCADE')] #[ORM\InverseJoinColumn(name: 'category_id', referencedColumnName: 'id', onDelete: 'RESTRICT')] + #[Assert\Count(min: 1, minMessage: 'Au moins une catégorie est obligatoire.')] #[Groups(['client_address:read', 'client_address:write'])] private Collection $categories; diff --git a/tests/Module/Commercial/Api/ClientAddressTest.php b/tests/Module/Commercial/Api/ClientAddressTest.php index 0c59207..93eb41c 100644 --- a/tests/Module/Commercial/Api/ClientAddressTest.php +++ b/tests/Module/Commercial/Api/ClientAddressTest.php @@ -167,8 +167,9 @@ final class ClientAddressTest extends AbstractCommercialApiTestCase public function testNonBillingAddressAcceptsEmptyBillingEmail(): void { $this->skipIfSitesModuleDisabled(); - $client = $this->createAdminClient(); - $seed = $this->seedClient('Non Billing Empty Email'); + $client = $this->createAdminClient(); + $seed = $this->seedClient('Non Billing Empty Email'); + $category = $this->createCategory('SECTEUR'); $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [ 'headers' => ['Content-Type' => self::LD], @@ -179,6 +180,7 @@ final class ClientAddressTest extends AbstractCommercialApiTestCase 'city' => 'Châtellerault', 'street' => '1 rue du Test', 'sites' => [$this->firstSiteIri()], + 'categories' => ['/api/categories/'.$category->getId()], ], ]); @@ -286,6 +288,29 @@ final class ClientAddressTest extends AbstractCommercialApiTestCase self::assertResponseStatusCodeSame(201); } + /** + * Spec-front § Adresse : au moins une categorie est obligatoire sur une + * adresse. POST sans categorie (mais avec site) -> 422. + */ + public function testAddressRequiresAtLeastOneCategory(): void + { + $this->skipIfSitesModuleDisabled(); + $client = $this->createAdminClient(); + $seed = $this->seedClient('Address No Cat'); + + $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [ + 'headers' => ['Content-Type' => self::LD], + 'json' => [ + 'postalCode' => '86100', + 'city' => 'Châtellerault', + 'street' => '1 rue du Test', + 'sites' => [$this->firstSiteIri()], + ], + ]); + + self::assertResponseStatusCodeSame(422); + } + /** * Retourne l'IRI du premier site seede (fixtures Sites). */ diff --git a/tests/Module/Commercial/Api/ClientSubResourceApiTest.php b/tests/Module/Commercial/Api/ClientSubResourceApiTest.php index dd7538c..a05ac33 100644 --- a/tests/Module/Commercial/Api/ClientSubResourceApiTest.php +++ b/tests/Module/Commercial/Api/ClientSubResourceApiTest.php @@ -110,9 +110,10 @@ final class ClientSubResourceApiTest extends AbstractCommercialApiTestCase public function testPostAddressNormalizesBillingEmail(): void { $this->skipIfSitesModuleDisabled(); - $client = $this->createAdminClient(); - $seed = $this->seedClient('Address Host'); - $siteIri = $this->firstSiteIri(); + $client = $this->createAdminClient(); + $seed = $this->seedClient('Address Host'); + $siteIri = $this->firstSiteIri(); + $category = $this->createCategory('SECTEUR'); $data = $client->request('POST', '/api/clients/'.$seed->getId().'/addresses', [ 'headers' => ['Content-Type' => self::LD], @@ -123,6 +124,7 @@ final class ClientSubResourceApiTest extends AbstractCommercialApiTestCase 'city' => 'Châtellerault', 'street' => '1 rue du Test', 'sites' => [$siteIri], + 'categories' => ['/api/categories/'.$category->getId()], ], ])->toArray();