feat(technique) : module Technique + taxonomie categories prestataires #89

Merged
matthieu merged 2 commits from feat/erp-m3-technique-module-taxonomie into develop 2026-06-12 14:19:15 +00:00
Owner

M3 — Ticket 1.1 : module Technique + taxonomie catégories prestataires

Prérequis de tout le M3 (répertoire prestataires). Spec : docs/specs/M3-prestataires/spec-back.md § 2.1 + § 2.4.

Contenu

  • Nouveau module Technique (src/Module/Technique/TechniqueModule.php) : ID=technique, LABEL=Technique, REQUIRED=false, permissions() (5 codes technique.providers.* : view / manage / accounting.view / accounting.manage / archive).
  • Activation dans config/modules.php/api/modules expose technique.
  • Layer front frontend/modules/technique/ (auto-détecté).
  • Seed taxonomie PRESTATAIRE : nouveau CategoryType (code PRESTATAIRE / label Prestataire) + 3 catégories (Maintenance industrielle, Nettoyage, Transport).
    • Migration racine idempotente Version20260612080000 (ON CONFLICT DO NOTHING + NOT EXISTS, jonction M2M category_category_type — schéma courant, pas l'ancien category_type_id).
    • Fixtures CategoryTypeFixtures / CategoryFixtures étendues (survivent au purger db-reset).

Critères d'acceptation

  • Module + permissions déclarées (app:sync-permissions → 5 codes en base)
  • TechniqueModule::class dans config/modules.php
  • Layer front
  • Seed CategoryType PRESTATAIRE (migration + fixture idempotente)
  • ≥ 3 catégories PRESTATAIRE
  • GET /api/categories?typeCode=PRESTATAIRE filtre correctement

Tests

  • TechniqueModuleTest : identité + jeu de 5 permissions figé.
  • CategoryPrestataireSeedTest : ?typeCode=PRESTATAIRE ne renvoie QUE le type PRESTATAIRE + pagination Hydra préservée.
  • make test : 589 tests OK · php-cs-fixer : 0 correction · make db-reset : type + 3 catégories présents, idempotent.

Hors-périmètre (tickets M3 suivants)

Section sidebar « Technique », personas RBAC E2E, et entités Provider* (l'écran /providers n'existe pas encore → pas de lien mort introduit ici).

## M3 — Ticket 1.1 : module Technique + taxonomie catégories prestataires Prérequis de tout le M3 (répertoire prestataires). Spec : `docs/specs/M3-prestataires/spec-back.md` § 2.1 + § 2.4. ### Contenu - **Nouveau module `Technique`** (`src/Module/Technique/TechniqueModule.php`) : `ID=technique`, `LABEL=Technique`, `REQUIRED=false`, `permissions()` (5 codes `technique.providers.*` : view / manage / accounting.view / accounting.manage / archive). - Activation dans `config/modules.php` → `/api/modules` expose `technique`. - **Layer front** `frontend/modules/technique/` (auto-détecté). - **Seed taxonomie PRESTATAIRE** : nouveau `CategoryType` (code `PRESTATAIRE` / label `Prestataire`) + 3 catégories (Maintenance industrielle, Nettoyage, Transport). - Migration racine idempotente `Version20260612080000` (`ON CONFLICT DO NOTHING` + `NOT EXISTS`, jonction M2M `category_category_type` — schéma courant, pas l'ancien `category_type_id`). - Fixtures `CategoryTypeFixtures` / `CategoryFixtures` étendues (survivent au purger `db-reset`). ### Critères d'acceptation ✅ - [x] Module + permissions déclarées (`app:sync-permissions` → 5 codes en base) - [x] `TechniqueModule::class` dans `config/modules.php` - [x] Layer front - [x] Seed CategoryType PRESTATAIRE (migration + fixture idempotente) - [x] ≥ 3 catégories PRESTATAIRE - [x] `GET /api/categories?typeCode=PRESTATAIRE` filtre correctement ### Tests - `TechniqueModuleTest` : identité + jeu de 5 permissions figé. - `CategoryPrestataireSeedTest` : `?typeCode=PRESTATAIRE` ne renvoie QUE le type PRESTATAIRE + pagination Hydra préservée. - `make test` : **589 tests OK** · `php-cs-fixer` : 0 correction · `make db-reset` : type + 3 catégories présents, idempotent. ### Hors-périmètre (tickets M3 suivants) Section sidebar « Technique », personas RBAC E2E, et entités `Provider*` (l'écran `/providers` n'existe pas encore → pas de lien mort introduit ici).
matthieu added 1 commit 2026-06-12 07:23:32 +00:00
feat(technique) : module Technique + taxonomie categories prestataires
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m13s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m27s
6ceef62056
Cree le nouveau module Technique (pole distinct du Commercial) prerequis du
M3 repertoire prestataires :
- TechniqueModule (ID=technique, REQUIRED=false) + 5 permissions RBAC
  technique.providers.* (view / manage / accounting.view / accounting.manage
  / archive), declarees pour app:sync-permissions.
- Activation dans config/modules.php + layer front frontend/modules/technique/.
- Seed taxonomie : nouveau CategoryType PRESTATAIRE + 3 categories
  (Maintenance industrielle, Nettoyage, Transport) via migration idempotente
  (ON CONFLICT / NOT EXISTS, jonction M2M category_category_type) ET fixtures
  CategoryType/Category (survivent au purger db-reset).
- Tests : structure du module (5 permissions figees) + filtre
  GET /api/categories?typeCode=PRESTATAIRE.

Inclut la spec back/front M3 et le RETEX M1.
matthieu added the backdbM3-Prestatairetype/feat labels 2026-06-12 07:23:53 +00:00
tristan reviewed 2026-06-12 07:55:02 +00:00
tristan left a comment
Owner

Revue de code (correctness + altitude) — 1 point substantiel (migration) + 1 note de contexte RBAC. Le reste de la MR (shape du module, tests, down(), colonnes de l'INSERT) est cohérent avec les conventions.

Revue de code (correctness + altitude) — 1 point substantiel (migration) + 1 note de contexte RBAC. Le reste de la MR (shape du module, tests, down(), colonnes de l'INSERT) est cohérent avec les conventions.
@@ -0,0 +77,4 @@
INSERT INTO category (name, code, created_at, updated_at)
SELECT :name, :code, NOW(), NOW()
WHERE NOT EXISTS (
SELECT 1 FROM category c WHERE c.code = :code AND c.deleted_at IS NULL
Owner

Idempotence partielle : le garde ne couvre que code, pas name (risque d'échec de migration sur une prod existante).

Le NOT EXISTS ne teste que c.code = :code, mais uq_category_name_active (créé en Version20260608120000) impose une unicité GLOBALE sur LOWER(name) parmi les actifs (CREATE UNIQUE INDEX uq_category_name_active ON category (LOWER(name)) WHERE deleted_at IS NULL).

Scénario d'échec concret : M3 est ajouté à une prod déjà en service (M1/M2). Si un utilisateur y a déjà créé une catégorie active nommée « Transport » ou « Nettoyage » (libellés très génériques) sous un autre code, alors :

  • le garde sur code passe (le code TRANSPORT est libre) ;
  • l'INSERT viole uq_category_name_active sur LOWER(name) → la migration make migration-migrate avorte sur une violation de contrainte unique.

À noter : la sœur Version20260605120000 (FOURNISSEUR) utilisait le même pattern mais tournait sous l'ancien index uq_category_name_type_active (unicité PAR TYPE) — la collision globale n'existait pas encore. Ce n'est plus le cas ici.

Recommandation — élargir le garde au nom (collision-safe plutôt que crash) :

WHERE NOT EXISTS (
    SELECT 1 FROM category c
    WHERE c.deleted_at IS NULL
      AND (c.code = :code OR LOWER(c.name) = LOWER(:name))
)

En cas de collision de nom, la catégorie n'est pas seedée (et donc pas rattachée au type via la jonction qui cible c.code = :code) — comportement dégradé acceptable, vs. un échec dur de la migration. La doc d'idempotence du docblock (§ « Idempotence ») mériterait d'être alignée en conséquence.

**Idempotence partielle : le garde ne couvre que `code`, pas `name` (risque d'échec de migration sur une prod existante).** Le `NOT EXISTS` ne teste que `c.code = :code`, mais `uq_category_name_active` (créé en `Version20260608120000`) impose une unicité **GLOBALE** sur `LOWER(name)` parmi les actifs (`CREATE UNIQUE INDEX uq_category_name_active ON category (LOWER(name)) WHERE deleted_at IS NULL`). Scénario d'échec concret : M3 est ajouté à une prod déjà en service (M1/M2). Si un utilisateur y a déjà créé une catégorie active nommée « Transport » ou « Nettoyage » (libellés très génériques) sous un autre `code`, alors : - le garde sur `code` passe (le code `TRANSPORT` est libre) ; - l'`INSERT` viole `uq_category_name_active` sur `LOWER(name)` → la migration `make migration-migrate` **avorte** sur une violation de contrainte unique. À noter : la sœur `Version20260605120000` (FOURNISSEUR) utilisait le même pattern mais tournait sous l'ancien index `uq_category_name_type_active` (unicité PAR TYPE) — la collision globale n'existait pas encore. Ce n'est plus le cas ici. Recommandation — élargir le garde au nom (collision-safe plutôt que crash) : ```sql WHERE NOT EXISTS ( SELECT 1 FROM category c WHERE c.deleted_at IS NULL AND (c.code = :code OR LOWER(c.name) = LOWER(:name)) ) ``` En cas de collision de nom, la catégorie n'est pas seedée (et donc pas rattachée au type via la jonction qui cible `c.code = :code`) — comportement dégradé acceptable, vs. un échec dur de la migration. La doc d'idempotence du docblock (§ « Idempotence ») mériterait d'être alignée en conséquence.
@@ -0,0 +44,4 @@
* Comptabilite et une a l'archivage (cf. spec-back M3 § 2.9 + § 5.1).
*
* @return array<int, array{code: string, label: string}>
*/
Owner

Contexte RBAC — 3 miroirs (règle ABSOLUE n°8) : OK pour ce ticket, à ne pas oublier au ticket suivant.

Ce module déclare 5 permissions technique.providers.* mais ne touche pas config/sidebar.php, frontend/tests/e2e/_fixtures/personas.ts ni SeedE2ECommand.php. C'est cohérent avec le périmètre annoncé (pas encore d'écran /providers, donc pas de permission testable au sens de .claude/rules/testing.md) et app:sync-permissions lit bien config/modules.php → les 5 codes sont upsertés.

Aucun garde-fou ne casse ici. Simple rappel : dès que l'écran prestataires arrivera (rattachement à un item sidebar + persona), les 3 miroirs devront bouger ensemble sous peine de drift / faux positif E2E.

**Contexte RBAC — 3 miroirs (règle ABSOLUE n°8) : OK pour ce ticket, à ne pas oublier au ticket suivant.** Ce module déclare 5 permissions `technique.providers.*` mais ne touche pas `config/sidebar.php`, `frontend/tests/e2e/_fixtures/personas.ts` ni `SeedE2ECommand.php`. C'est cohérent avec le périmètre annoncé (pas encore d'écran `/providers`, donc pas de permission *testable* au sens de `.claude/rules/testing.md`) et `app:sync-permissions` lit bien `config/modules.php` → les 5 codes sont upsertés. Aucun garde-fou ne casse ici. Simple rappel : dès que l'écran prestataires arrivera (rattachement à un item sidebar + persona), les 3 miroirs devront bouger **ensemble** sous peine de drift / faux positif E2E.
tristan marked this conversation as resolved
matthieu added 1 commit 2026-06-12 14:18:36 +00:00
Merge branch 'develop' into feat/erp-m3-technique-module-taxonomie
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 2m34s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m33s
2331a846fd
matthieu merged commit d97b9ce6d0 into develop 2026-06-12 14:19:15 +00:00
matthieu deleted branch feat/erp-m3-technique-module-taxonomie 2026-06-12 14:19:17 +00:00
Sign in to join this conversation.