feat(commercial) : catégories de type Adresse pour les blocs adresse (client + fournisseur) (#147)
Auto Tag Develop / tag (push) Successful in 12s

## Objectif

Introduit un `CategoryType` dédié **ADRESSE** (module Catalog) consommé par le champ « Catégorie » des blocs adresse, en remplacement de la réutilisation détournée des types CLIENT / FOURNISSEUR.

## Changements

**Backend**
- Migration de seed du type ADRESSE + 6 catégories : Siège, Contact issues, Facturation, Livraison, Approvisionnement, Méthaniseur (idempotente, réversible) ; fixtures alignées.
- `ClientAddress` : validation blacklist (DISTRIBUTEUR/COURTIER) remplacée par une whitelist « catégories de type ADRESSE uniquement ».
- `SupplierAddress` : type requis FOURNISSEUR → ADRESSE (le bloc principal fournisseur reste en FOURNISSEUR).

**Frontend**
- Ref dédiée `addressCategories` (`?typeCode=ADRESSE`) dans les composables référentiels client et fournisseur.
- Pages new/edit client et fournisseur câblées sur les blocs adresse.

**Tests**
- `CategoryAdresseSeedTest` (miroir du test PRESTATAIRE).
- Adaptation des tests d'adresse client/fournisseur (sémantique whitelist ADRESSE) + helper `createAddressCategory()`.

## Vérifications
- Back : suites Catalog + Architecture + adresse/fournisseur vertes (le flake JWT connu du hook est sans rapport, tests verts en isolation).
- Front : Vitest vert (composables référentiels + ciblés).
- php-cs-fixer : 0 correction ; eslint : OK.

Reviewed-on: #147
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #147.
This commit is contained in:
2026-06-25 07:26:21 +00:00
committed by Autin
parent 2e50a760c6
commit efded9fd40
22 changed files with 444 additions and 168 deletions
@@ -42,7 +42,7 @@ use Symfony\Component\Validator\Context\ExecutionContextInterface;
* - sites : SiteInterface (module Sites) via resolve_target_entities
* - contacts : ClientContact (meme module)
* - categories : CategoryInterface (module Catalog) via resolve_target_entities
* — codes DISTRIBUTEUR/COURTIER interdits (RG-1.29, validateCategoryCodes, ERP-78)
* — type ADRESSE attendu (validateCategoryType)
*
* Audite (#[Auditable]) + Timestampable/Blamable.
*
@@ -96,11 +96,11 @@ class ClientAddress implements TimestampableInterface, BlamableInterface, Client
use TimestampableBlamableTrait;
/**
* RG-1.29 (ERP-78) : ces codes de categorie decrivent une relation entre
* clients (distributeur / courtier) et n'ont pas de sens sur une adresse.
* Toute autre categorie du type CLIENT est autorisee.
* Seules les categories PORTANT ce type sont autorisees sur une adresse client.
* S'appuie sur CategoryInterface::getCategoryTypeCodes() (multi-type — pas
* d'import du module Catalog, regle ABSOLUE n°1).
*/
private const array FORBIDDEN_CATEGORY_CODES = ['DISTRIBUTEUR', 'COURTIER'];
private const string REQUIRED_CATEGORY_TYPE_CODE = 'ADRESSE';
#[ORM\Id]
#[ORM\GeneratedValue]
@@ -215,7 +215,7 @@ class ClientAddress implements TimestampableInterface, BlamableInterface, Client
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).
// Categories de type ADRESSE uniquement (validateCategoryType).
/** @var Collection<int, CategoryInterface> */
#[ORM\ManyToMany(targetEntity: CategoryInterface::class)]
#[ORM\JoinTable(name: 'client_address_category')]
@@ -335,20 +335,19 @@ class ClientAddress implements TimestampableInterface, BlamableInterface, Client
}
/**
* RG-1.29 (ERP-78) : une adresse interdit les categories de code
* DISTRIBUTEUR / COURTIER — elles decrivent une relation entre clients
* (RG-1.03) et n'ont pas de sens sur une adresse physique -> 422 avec
* violation sur le champ `categories`. Toute autre categorie (type unique
* CLIENT) est acceptee. S'appuie sur CategoryInterface::getCode() (pas
* d'import du module Catalog — regle ABSOLUE n°1).
* Toute categorie posee sur une adresse client doit etre de type ADRESSE ->
* sinon 422 avec violation sur le champ `categories`. S'appuie sur
* CategoryInterface::getCategoryTypeCodes() (multi-type — la categorie est
* acceptee des qu'elle PORTE le type ADRESSE ; pas d'import du module Catalog,
* regle ABSOLUE n°1).
*/
#[Assert\Callback]
public function validateCategoryCodes(ExecutionContextInterface $context): void
public function validateCategoryType(ExecutionContextInterface $context): void
{
foreach ($this->categories as $category) {
if ($category instanceof CategoryInterface
&& in_array($category->getCode(), self::FORBIDDEN_CATEGORY_CODES, true)) {
$context->buildViolation('Type de catégorie non autorisé sur une adresse.')
&& !in_array(self::REQUIRED_CATEGORY_TYPE_CODE, $category->getCategoryTypeCodes(), true)) {
$context->buildViolation('Type de catégorie non autorisé (ADRESSE attendu).')
->atPath('categories')
->addViolation()
;