PHPUnit suite for the M0 Catalog module covering the 17 business rules of
the spec :
- CategoryPermissionsTest (RG-1.01) — 4 personas + admin + anonymous on GET / POST / PATCH / DELETE
- CategoryValidationTest (RG-1.02 to RG-1.06) — name NotBlank + trim + length + categoryType required / must exist
- CategoryUniqueTest (RG-1.07) — 409 case-insensitive, multi-type allowed, recreate after soft delete
- CategoryListTest (RG-1.08 to RG-1.10) — soft-delete filter, includeDeleted flag, name ASC sort
- CategoryGetTest (RG-1.11) — 404 on soft-deleted, 200 with flag, 404 on not found
- CategoryDeleteTest (RG-1.12 / RG-1.13) — soft delete, deletedAt unwriteable via PATCH, 404 on already deleted
- CategoryAuditTest — audit_log written on create / update / soft delete with correct performed_by
- CategoryTimestampableBlamableTest (RG-1.15 / RG-1.16) — blame admin on POST, null in console context, frozen createdBy on PATCH, updated_* on soft delete
- EntitiesAreTimestampableBlamableTest (RG-1.17) — stays green (already shipped in ERP-52)
Side fixes uncovered by the suite :
- Category.php : add normalizer 'trim' to NotBlank + Length so a whitespace-only
name returns 422 (RG-1.02 + RG-1.03 alignment, the Processor trim was running
after validation).
- makefile : recreate the partial index uq_category_name_type_active in
test-db-setup. doctrine:schema:update drops the unexpressable index after
migrations, which made RG-1.07 silently pass (duplicate POST -> 201 instead
of 409). The DDL is now restored after schema:update via dbal:run-sql.
Tests : 311 total (248 + 63 new), 1071 assertions, 0 failure, 0 risky.
Les logs montrent que chaque operation actions/cache attend ~4m30 avant
ETIMEDOUT sur le serveur de cache du runner Gitea (51.91.78.99:39531) :
- cache: npm de setup-node = tout le 'Setup Node 22' (271s)
- cache node_modules et cache .nuxt : timeouts additionnels
- cache Composer cote backend : meme risque
Node 22 est deja dans le tool-cache (install instantane), npm ci a froid
~30s, build ~20s : le caching n'apportait rien ici. A re-activer si le
serveur de cache du runner est repare.
- remplace build:dist (nuxt generate + prerender inutile en SPA) par nuxt build
- cache node_modules sur hash du lockfile, npm ci uniquement en cache miss
- regenere les types Nuxt (postinstall) en cache hit
- cache des artefacts .nuxt / Vite avec restore-keys pour eviter le build a froid
## Contexte
Ticket Lesstime #46 — position 0.4 (M0 Catalog, quick win).
Expose `CategoryType` en lecture seule pour alimenter le `<MalioSelect>` du formulaire `Category` côté front. Pas d'écriture exposée au M0 (table vide à la livraison).
## Mode stacked PR
⚠ **Cible (base branch) : `feature/ERP-45-implementer-provider-processor-category`** (PAS develop).
Quand MR ERP-45 sera mergée sur develop, repointer la cible de cette MR vers develop.
## Changement
Le gros du travail (`#[ApiResource(operations: [GetCollection, Get])]`, security `is_granted('catalog.categories.view')`, groupes de sérialisation) a été livré dans le ticket ERP-44. Cette MR ajoute uniquement ce qui manquait à la spec § 4.6 :
- `order: ['label' => 'ASC']` sur l'opération `GetCollection` → tri alphabétique stable pour le select front.
## Critères d'acceptation (spec § 4.6)
- [x] `GET /api/category_types` retourne tous les `CategoryType` triés par `label ASC`
- [x] `GET /api/category_types/{id}` retourne le détail
- [x] POST / PATCH / DELETE → 404 (opérations non déclarées)
- [x] Security `is_granted('catalog.categories.view')` sur les 2 opérations
- [x] `make php-cs-fixer-allow-risky` passe (0 fix)
## Vérifications
\`\`\`
$ php bin/console debug:router | grep category_type
_api_/category_types{._format}_get_collection GET /api/category_types.{_format}
_api_/category_types/{id}{._format}_get GET /api/category_types/{id}.{_format}
\`\`\`
→ Exactement 2 routes générées, aucune POST/PATCH/DELETE.
- \`make php-cs-fixer-allow-risky\` ✓ (0 fix)
- \`make test\` ✓ (248/248)
---------
Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #18
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
## Mode stacked PR — cible `feature/ERP-44-creer-entites-category`
> ⚠ Cette MR a pour base la branche ERP-44 (en review). Quand ERP-44 sera mergée sur develop, **repointer la cible vers develop**.
## Résumé
- Provider `CategoryProvider` : filtre soft-delete par défaut (RG-1.08), `?includeDeleted=true` (RG-1.09), tri name ASC (RG-1.10), 404 si soft-deleted hors flag (RG-1.11).
- Processor `CategoryProcessor` : trim du `name` (RG-1.03), conversion DELETE → UPDATE (RG-1.12), mapping `UniqueConstraintViolationException` → HTTP 409 avec message exact (RG-1.07).
- Câblage Provider/Processor dans `#[ApiResource]` de `Category`. Provider câblé aussi sur Patch + Delete (au-delà du scope strict du ticket) pour fermer la fuite RG-1.11 sur PATCH.
- `DoctrineCategoryRepository` expose `createListQueryBuilder($includeDeleted)`.
## Décisions notables
- **Filtre soft-delete via QueryBuilder** (choix `a` du ticket) : pas de filtre Doctrine global, lisibilité directe.
- **Pas de `remove_processor` injecté** : la DELETE est convertie en UPDATE via le `persist_processor`. API Platform 4 utilise le processor déclaré sur l'opération sans fallback automatique.
- **Provider sur Patch + Delete aussi** : décision prise pendant le dev pour fermer une fuite RG-1.11 sur PATCH. Coût : 2 lignes dans `Category.php`.
## Tests
- `make php-cs-fixer-allow-risky` ✓
- `make test` ✓ (248 tests, 0 régression — pas de test métier Category, c'est ERP-48)
- Tests manuels curl ✓ — 8 cas RG-1.03 → RG-1.13 validés (détail dans le ticket Lesstime #45)
## Tickets
- Lesstime : #45 (ERP-45) → En review
- Position M0 : 0.3
- Spec : `docs/specs/M0-categories/spec-back.md` § 4.1 + § 4.3 + § 4.4 + § 4.5
## Suite
- ERP-46 (0.4 CategoryType lecture seule) — base : cette branche
---------
Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #17
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
## Objectif
Couche Domain DDD du module Catalog (ticket M0 · position 0.2). Crée les entités `Category` et `CategoryType`, leurs repositories, et branche le pattern Timestampable + Blamable Shared.
> **Mode stacked PR** : cible `feature/ERP-43-migrer-tables-category`. Quand la MR ERP-43 sera mergée sur develop, Matthieu repointera la cible de cette MR vers develop.
## Contenu
- **`Category`** : `#[ApiResource]` (GetCollection, Get, Post, Patch, Delete), `#[Auditable]`, `TimestampableBlamableTrait` + interfaces, asserts (`NotBlank`/`Length` sur `name`, `NotNull` sur `categoryType`), soft delete via `deletedAt`. Provider/Processor branchés au ticket 0.3 (ERP-45).
- **`CategoryType`** : référentiel statique en lecture seule (GetCollection + Get), embarqué dans `Category` via le groupe `category:read`. Pas de Trait — whitelisté dans `EntitiesAreTimestampableBlamableTest::EXCLUDED` (RG-1.17).
- **Repositories** : interfaces Domain + implémentations Doctrine.
- **`config/packages/doctrine.yaml`** : mapping ORM `Catalog` inconditionnel (miroir de `Sites`) — nécessaire pour que l'ORM reconnaisse les entités. La déclaration du module (`config/modules.php`) reste pour le ticket 0.5 (ERP-47).
- Groupes : `category:read` / `category:write` + `default:read` (expose les 4 colonnes du Trait).
## Notes techniques
- Index nommés déclarés sur les entités pour matcher la migration (cf. Role/Permission/Site).
- L'index unique partiel `uq_category_name_type_active` (`LOWER(name), category_type_id WHERE deleted_at IS NULL`) reste possédé par la seule migration : Doctrine ORM ne sait pas exprimer un index fonctionnel + partiel. Seul diff résiduel de `doctrine:schema:validate`.
## Tests
- `make php-cs-fixer-allow-risky` ✓
- `make test` ✓ (248 tests, 0 échec)
- `make db-reset` ✓
- `debug:router` ✓ (7 routes exposées)
- `doctrine:schema:validate` : mapping correct
---------
Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #15
Reviewed-by: Autin <tristan@yuno.malio.fr>
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.