[ERP-78] Refonte taxonomie Catégories : type unique CLIENT + Category.code + RG-1.03/1.29 par code (#42)
Auto Tag Develop / tag (push) Successful in 8s
Auto Tag Develop / tag (push) Successful in 8s
Refonte de la taxonomie Catégories (décision produit 01/06) : le modèle est inversé. ## Modèle - **UN SEUL `category_type` : CLIENT**. `Distributeur` / `Courtier` / `Secteur` / `Autre` (+ catégories métier) deviennent des `Category` rattachées à CLIENT. - Filtrage métier sur un **`code` stable porté par `Category`** (NOT NULL, unique partiel `uq_category_code`), slug MAJUSCULE auto-généré du nom (`CategoryCodeGenerator`), figé à la création, exposé en **lecture seule**. ## Contenu - **M0** : `Category.code` (entité + migration corrective `Version20260602100000` au namespace racine + `COMMENT ON COLUMN` + catalogue + ligne `test-db-setup`). Retrofit `Version20260528120000` rendu conscient des colonnes. - **Seed** : type unique CLIENT, catégories codées (`Distributeur→DISTRIBUTEUR`, etc.), anciens types supprimés. Fixtures `CategoryType`/`Category`/`Client` alignées. - **RG-1.03** : `ClientProcessor::hasCategoryCode` — un distributor/broker doit porter la `Category` de code `DISTRIBUTEUR`/`COURTIER`. Filtre liste/export `categoryType` → `categoryCode`. - **RG-1.29** : `ClientAddress::validateCategoryCodes` — denylist des codes `DISTRIBUTEUR`/`COURTIER` sur une adresse (toute autre catégorie autorisée). - **Specs** M0/M1 (back + front) amendées. ## Tests `make php-cs-fixer-allow-risky` OK ; `make db-reset` OK (type unique CLIENT + 11 catégories codées, idempotent) ; `make test` **443 vert**. Ajouts : RG-1.03 courtier, génération/unicité/lecture-seule du code (`CategoryCodeTest`). ## Coordination - #76 (#500) : RG-1.29 réécrite ici sur le nouveau modèle ; #76 ne garde que le gap 2 (mapping CHECK adresse → 422), indépendant de la taxonomie. - ERP-68 (#486) : fixtures démo (déjà mergées via #41) adaptées ici au type unique CLIENT + codes. - Front #480–483 : selects Catégorie / distributeur / courtier basés sur le `code` (`?categoryCode=`), plus le type. Décisions actées avec le PO : `code` NOT NULL auto-généré (slug) ; périmètre complet (réécriture RG + fixtures déjà mergées). --------- Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #42 Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
This commit was merged in pull request #42.
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Shared\Infrastructure\Database;
|
||||
|
||||
/**
|
||||
* Miroir SQL de `CategoryCodeGenerator::slugify()` (module Catalog, ERP-78).
|
||||
*
|
||||
* Le `code` d'une `Category` est un slug MAJUSCULE deterministe du nom. A
|
||||
* l'execution (POST/PATCH API), il est genere en PHP par `CategoryCodeGenerator`
|
||||
* via `AsciiSlugger`. Mais la migration corrective `Version20260602100000` doit
|
||||
* backfiller le `code` des categories pre-existantes en SQL pur (le backfill
|
||||
* tourne dans le plan `addSql`, sans acces aux services applicatifs).
|
||||
*
|
||||
* Deux implementations d'un meme slug = risque de derive : un nom accentue
|
||||
* comme « Independant » doit produire le MEME code (`INDEPENDANT`) quel que soit
|
||||
* le chemin. Cette classe est la SOURCE UNIQUE de l'expression SQL ; son egalite
|
||||
* avec le générateur PHP est verrouillee par `CategoryCodeSqlSlugTest`.
|
||||
*
|
||||
* Domaine couvert : noms francais / Latin-1 (tous les accents, minuscule +
|
||||
* majuscule, translitteres vers l'ASCII comme le fait `AsciiSlugger`). Limite
|
||||
* connue et assumee : les ligatures (`Œ`->`OE`, `ß`->`SS`) ne sont PAS gerees
|
||||
* par `translate()` (mapping 1->1 uniquement) ; elles n'apparaissent pas dans
|
||||
* les noms de categories CLIENT et le backfill ne s'execute de toute facon que
|
||||
* sur des bases dev deja peuplees (en prod la table `category` est vide).
|
||||
*/
|
||||
final class CategoryCodeSql
|
||||
{
|
||||
/** Longueur maximale de la colonne `category.code` (cf. CategoryCodeGenerator). */
|
||||
private const int MAX_LENGTH = 50;
|
||||
|
||||
/**
|
||||
* Accents Latin-1 (minuscules puis majuscules) translitteres vers leur
|
||||
* equivalent ASCII minuscule — `UPPER()` repasse tout en majuscule ensuite.
|
||||
* `translate()` mappe caractere a caractere : `ACCENT_FROM` et `ACCENT_TO`
|
||||
* doivent avoir EXACTEMENT le meme nombre de caracteres.
|
||||
*/
|
||||
private const string ACCENT_FROM = 'àâäáãåçéèêëíìîïñóòôöõúùûüýÿÀÂÄÁÃÅÇÉÈÊËÍÌÎÏÑÓÒÔÖÕÚÙÛÜÝŸ';
|
||||
private const string ACCENT_TO = 'aaaaaaceeeeiiiinooooouuuuyyaaaaaaceeeeiiiinooooouuuuyy';
|
||||
|
||||
/**
|
||||
* Expression SQL produisant le slug du `$column` donne (ex: `name`, `c.name`).
|
||||
* Reproduit fidelement `CategoryCodeGenerator::slugify` : translitteration des
|
||||
* accents, separateurs non alphanumeriques reduits a `_`, MAJUSCULE, borne a
|
||||
* 50, `_` de bord retires, fallback `CATEGORY` si vide.
|
||||
*/
|
||||
public static function slugExpression(string $column): string
|
||||
{
|
||||
return sprintf(
|
||||
"COALESCE(NULLIF(TRIM(BOTH '_' FROM "
|
||||
."LEFT(UPPER(REGEXP_REPLACE(translate(%s, '%s', '%s'), '[^A-Za-z0-9]+', '_', 'g')), %d)"
|
||||
."), ''), 'CATEGORY')",
|
||||
$column,
|
||||
self::ACCENT_FROM,
|
||||
self::ACCENT_TO,
|
||||
self::MAX_LENGTH,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user