Les operations Post/Patch de Product n'avaient pas collectDenormalizationErrors :
un null/type invalide sur une relation (category) levait un 400 qui
court-circuitait toute la validation -> aucune violation propertyPath, donc
aucune erreur mappee sous les champs (ajout comme modification).
- Product : collectDenormalizationErrors: true sur Post + Patch (miroir
Client/Supplier/WeighingTicket) -> 422 avec propertyPath au lieu de 400.
- useProductForm : on omet la cle 'category' du payload quand aucune categorie
n'est choisie (envoyer null casserait la denormalisation IRI et masquerait les
autres violations) -> le back renvoie les 6 violations d'un coup, dont le
NotNull propre sur category.
La disponibilité « type de stockage par site » relèvera de la future entité
Stockage (site + type), pas du référentiel. On retire donc la jointure M2M
storage_type_site et le filtrage du multi-select par site (RG-6.06 revue) :
- migration : DROP storage_type_site + seed idempotent des 10 types (prod-safe,
ON CONFLICT) ;
- StorageType : référentiel plat (plus de relation sites) ;
- Product : suppression du Assert\Callback de disponibilité par site ;
- provider/repository : /storage_types renvoie tous les types (plus de ?siteId[]) ;
- front : useStorageTypeOptions charge tout dans loadReferentials, setSites sans
cascade/purge ;
- fixture, ColumnCommentsCatalog, tests et spec-back M6 alignés.
La liste vivait en pages/admin/products.vue, en cohabitation avec les enfants
products/new.vue et products/[id]/edit.vue. Nuxt transforme alors products.vue
en route PARENT de /admin/products/* : sans <NuxtPage/>, les enfants ne sont
jamais rendus (cliquer « Ajouter » change l'URL mais ré-affiche la liste).
Renommage en pages/admin/products/index.vue (pattern du module carriers) : la
liste, l'ajout et l'édition deviennent des routes sœurs, sans wrapper parent.
Finalisation i18n du M6.
- sidebar.catalog.products : « Catalogue produit » → « Produits »
- message des onglets placeholder Fournisseurs/Clients : « Cet onglet est en cours de développement » (passé à ComingSoonPlaceholder)
Le libellé d'audit audit.entity.catalog_product (« Produit ») est déjà présent
(posé avec l'entité Product #[Auditable]) — AuditableEntitiesHaveI18nLabelTest vert.
Les libellés de champs et d'états (Achat/Vendu/Autre) ont été posés en ERP-205/206.
Ecran d'entree du catalogue produit (admin-only) : liste paginee
(usePaginatedList), drawer de filtres (categorie/etat/sites), export
XLSX et navigation vers creation/edition.
- colonnes Nom / Numero (code) / Categorie (category.name), tri name ASC serveur
- filtres mappes sur les query params du provider (categoryId, state, siteId[])
- etat du tableau 100% local (jamais dans l'URL)
- type Product calque sur le contrat JSON capture (ERP-203)
- i18n admin.products ; 11 tests Vitest
## Contexte
ERP-117 — correctifs frontend sur l'ecran de gestion des categories et alignement des boutons d'action des ecrans admin.
## Changements
### Drawer categories
- Titre stable « Modifier la categorie » (plus de bascule view → edit selon l'etat « dirty »), aligne sur les drawers simples du projet.
- Bouton Enregistrer toujours actif : il sauvegarde a tout moment, meme sans modification (PATCH du payload complet `name` + `categoryTypes`, comme `SiteDrawer`).
- Champ « Types de categorie » : suppression du label « Selectionner un ou plusieurs types ».
### Alignement des boutons admin
- Ecran Categories : ordre des boutons Filtres avant Ajouter + gap reduit (`gap-8`), comme le repertoire client.
- Boutons d'ajout admin (categories, roles, sites) passes en `variant=secondary`.
- Boutons Filtres (categories, audit-log, clients) en `tertiary` simple : suppression des surcharges de classe, icone a gauche 24px.
## Tests
- `useCategoryForm` mis a jour (PATCH payload complet).
- `make nuxt-test` : 256/256 OK.
- `make nuxt-lint` : OK.
Reviewed-on: #77
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
## Contexte
Une `Category` ne pouvait appartenir qu'à **un seul** `CategoryType` (ManyToOne). Le besoin métier : plusieurs types par catégorie. Cette MR fait passer la relation en **ManyToMany** et ajoute le bouton **« Filtres »** à droite de la liste des catégories (modèle Répertoire Clients).
Slice vertical complet (le passage M:N casse mécaniquement le contrat inter-module `CategoryInterface`, consommé par la RG-2.10 fournisseurs).
## Volet A — Relation M:N
- `Category.categoryType` (ManyToOne) → `categoryTypes` (ManyToMany, jonction `category_category_type`). Au moins un type obligatoire (`Assert\\Count(min:1)`).
- **Unicité du nom GLOBALE** parmi les actifs (`uq_category_name_active`, remplace `uq_category_name_type_active`). Message 409 reformulé.
- Migration : table de jonction + backfill + drop colonne `category_type_id` + nouvel index. Validée **rejouable sur base fraîche**.
- Contrat Shared : `getCategoryTypeCode()` → `getCategoryTypeCodes(): array`. `Supplier`/`SupplierAddress`/`SupplierFixtures` revalident « contient FOURNISSEUR » (RG-2.10).
- Provider/Repository : filtre type via sous-requête `EXISTS` (ne tronque pas la collection embarquée), eager-load anti-N+1.
## Volet B — Bouton « Filtres »
- Drawer recherche par nom + types multi (OR). Compteur de filtres actifs. État local, jamais persisté en URL.
- Back : filtres `?name=` et `?typeId[]=` sur la collection.
## Front
- Multi-select `MalioSelectCheckbox`, `useCategoryForm` en `categoryTypeIds[]`, colonne « Types », clés i18n.
## Tests / vérifs
- `make test` : **582 tests, 2474 assertions, 0 échec** ✅
- `make nuxt-test` : **236 tests** ✅
- `make php-cs-fixer-allow-risky` ✅
- Migration rejouée sur base fraîche (`make db-reset`) ✅
- Nouveau `CategoryFilterTest` (name partiel + typeId[] OR + multi-type non dupliqué)
---------
Co-authored-by: Matthieu <contact@malio.fr>
Reviewed-on: #75
Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
## Summary
Mise a jour de la lib `@malio/layer-ui` de `^1.7.2` vers `^1.7.3` et adaptation des ecrans pour deux changements visuels apportes par la lib :
- Le slot message (`error || success || hint`) sous les composants Malio est desormais **toujours rendu** dans le DOM (~16px en bas), pour eviter le saut de mise en page quand un champ passe en erreur.
- Nouvelle classe utilitaire `w-m-btn-action` pour standardiser la largeur des boutons d'action (remplacement du fix `w-[150px]`).
## Details
- **Bump dependance** : `frontend/package.json` + `frontend/package-lock.json` (`@malio/layer-ui` `^1.7.2` -> `^1.7.3`)
- **Boutons d'action** : 12 occurrences `button-class=\"w-[150px]\"` migrees vers `button-class=\"w-m-btn-action\"` dans `CategoryDrawer`, `RoleDrawer`, `SiteDrawer`, `UserRbacDrawer`, `audit-log`
- **Espacements formulaires** : rabotage des `gap-*` / `space-y-*` sur les conteneurs colonne (forms drawers, listes de checkbox, grille dates du drawer filtres audit-log, accordeon permissions, login) pour absorber le slot message desormais toujours present (16px)
- **Alignements verticaux** : compensation `pb-4` sur les voisins non-Malio dans les conteneurs `items-center` — puce couleur du `SiteDrawer` (`<div class=\"shrink-0 pb-4\">` autour du span) et labels `Du` / `Au` du drawer filtres `audit-log` (`<span class=\"pb-4\">`)
- **Layout** : reduction du padding lateral xl: dans `default.vue` (`xl:px-[170px]` -> `xl:px-[44px]`)
## Test plan
- [x] `make nuxt-test` (103/103 OK localement)
- [x] `make test` (322/322 OK localement)
- [x] Validation visuelle drawer Categories (Create / Edit / Delete)
- [x] Validation visuelle drawer Roles + accordeon permissions
- [x] Validation visuelle drawer Sites (puce couleur centree avec le champ)
- [x] Validation visuelle drawer Users RBAC
- [x] Validation visuelle page Audit Log (table + drawer filtres : dates Du/Au alignees, checkboxes correctement espacees)
- [x] Validation visuelle page Login (espacements entre champs / bouton / version)
## Suite
Un fix upstream `@malio/layer-ui` sera necessaire pour corriger l'alignement du label `Lignes :` dans la pagination de `MalioDataTable` (slot vide du `MalioSelect` interne) — prompt prepare a coller dans une session sur le repo de la lib.
Reviewed-on: #33
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
## Contexte
Ticket Lesstime : #73 (id 492) — volet front de la pagination (groupe Transversal).
Dépend du back ERP-72 (déjà mergé sur develop). Pas de spec docs/specs ; référence = description #73 + .claude/rules/frontend.md.
## Implémentation
- Composable réutilisable `usePaginatedList` (`frontend/shared/composables/`) générique, branché directement sur `MalioDataTable` (props page/perPage/totalItems + events update:page/update:per-page).
- Force `Accept: application/ld+json` (sans Accept, API Platform renvoie un tableau plat sans pagination).
- Migration des pages admin existantes (M0 catégories, Sites, Utilisateurs, Rôles) vers le composable.
- Refactor de `useCategoriesAdmin` : ne porte plus la liste paginée (déplacée vers `usePaginatedList<Category>` dans la page) et concentre son rôle sur le référentiel `CategoryType` (chargé en une fois via `?pagination=false`, échappatoire prévue par `pagination_client_enabled: true` côté back).
- Cas limites couverts : liste vide (pas de contrôle pagination affiché), page hors borne après filtre (retombe sur la dernière page valide), items/page 10/25/50, reset filtres/tri, swallow erreur réseau.
- Pattern « liste paginée » documenté dans `.claude/rules/frontend.md` (section dédiée + exemple).
## Décision URL
Le ticket suggérait « idéalement page/tri/filtre dans l'URL » — arbitré explicitement par Tristan en faveur de la règle ABSOLUE n°6 du CLAUDE.md (state local uniquement, jamais persisté dans l'URL). Aucun reflet URL implémenté ; comportement homogène entre toutes les listes migrées.
## Tests
- `make nuxt-test` : 101/101 OK (22 nouveaux tests sur `usePaginatedList`, 6 anciens tests `useCategoriesAdmin.fetchAll` retirés en cohérence avec la refacto).
- Vérification manuelle dans le navigateur (`make dev-nuxt`) : Sites, Utilisateurs, Rôles, Catégories affichent le sélecteur `Lignes : 10` et les boutons Prev/Next ; audit-log (non migré, composable spécifique) intact avec ses 3 pages.
- Aucun test E2E ajouté (règle d'or projet).
- Pre-commit hook : ESLint + PHPUnit 322/322 OK.
## Hors périmètre
- `audit-log.vue` non migré : composable `useAuditLog` spécifique (cache partagé page/timeline, filtres complexes, persistance URL préexistante). Refactor risqué et net-zéro pour ERP-73.
- M1 répertoire clients : pas encore livré sur develop (seules les specs sont mergées via #23). Le futur écran consommera `usePaginatedList` dès sa création.
Reviewed-on: #30
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
Lien Lesstime : #50
## Résumé
Refacto : extraction de la logique fetch/CRUD inline de la page categories (ERP-49) vers deux composables dédiés, conformément au pattern Starseed (useSidebar / useModules).
- **useCategoriesAdmin** : singleton state (`categories` + `types` + `loading` + `error`). Pré-chargement des types au mount de la page (au lieu d'un fetch par ouverture du drawer). Reset au logout via `onAuthSessionCleared` + appel explicite dans `logout.vue`.
- **useCategoryForm** : state local par form (pas singleton, contrairement à `useCategoriesAdmin`). Valide côté client en miroir des RG back (RG-1.02 / RG-1.04 / RG-1.05), mappe les erreurs 409 (RG-1.07 doublon) et 422 (violations API Platform) sur les bons champs. `submitCreate` / `submitUpdate` / `submitDelete` renvoient la ressource ou `null` pour découpler la décision de fermeture du drawer.
La page et le drawer deviennent purement présentationnels — aucune régression UX attendue (mêmes validations, mêmes toasts, même bascule view → edit via `isDirty` exposé par le composable).
## Décisions
- `useCategoriesAdmin` porte aussi les types (`fetchTypes`), pas seulement `categories` — sinon le drawer continuerait à fetcher tout seul et la refacto n'aurait rien centralisé.
- `buildCreatePayload` retourne `Record<string, unknown>` (pas `CategoryCreateInput`) car la signature `useApi.post(body: AnyObject)` n'accepte pas les types stricts (variance TS).
- Reset au logout : double mécanisme conservé (auto via `onAuthSessionCleared` pour 401, explicite dans `logout.vue` pour logout volontaire — pattern existant Starseed).
## Tests
- `npx nuxi typecheck` ✓ 0 erreur nouvelle (1 erreur pré-existante sur `modules/catalog/nuxt.config.ts` héritée d'ERP-49)
- `make nuxt-test` ✓ 43/43, 0 régression
- PHPUnit ✓ 311/311 (pre-commit)
- Manuel navigateur : à valider (cahier de test consigné dans Lesstime #50)
## ⚠ Note d'intégration
La branche contient encore les 3 commits ERP-49 (`4046910`, `216f388`, `934a12b`) car elle a été créée depuis la branche ERP-49 avant son merge sur develop. Selon l'ordre de merge : soit ERP-49 est mergée d'abord (cette MR ne contiendra plus que le commit ERP-50 après rebase auto), soit cette MR embarque tout l'historique catalog.
Reviewed-on: #25
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
## Contexte
Ticket Lesstime : [#49](https://lesstime.malio.fr/tasks/460) — premier ticket front du M0 (Gestion des catégories).
Suit la chaîne back ERP-43..48 mergée sur develop.
## Contenu first draft (Claude Code)
- Page Nuxt `/admin/categories` (`MalioDataTable` + bouton `+ Ajouter`)
- Composant `<CategoryDrawer>` : modes création / consultation / édition, transition auto view → edit à la première modification, validation client miroir RG-1.02 (name requis) / RG-1.04 (longueur 2-120) / RG-1.05 (type requis), mapping erreurs 409 (doublon) et 422 (violations)
- Composant `<CategoryDeleteModal>` : confirmation suppression (soft delete RG-1.12)
- Types TS `Category`, `CategoryType`, `User`
- i18n `admin.categories.*` ajouté dans `fr.json`
- Fix latent en passant : ajout de `'categories'` à `AdminLinkSlug` du Page Object e2e (oublié lors d'ERP-47 quand l'item sidebar a été ajouté)
## Décisions marquantes
- Logique `fetch` inline dans `categories.vue` (sera extraite en composables `useCategoriesAdmin` + `useCategoryForm` au ticket ERP-50 / 0.8)
- Drawer dans composant séparé pour réutilisabilité
- Aucun état de tableau persisté dans l'URL (règle ABSOLUE n°6)
- Tous les composants formulaires sont `Malio*` (`MalioDataTable`, `MalioInputText`, `MalioSelect`, `MalioButton`, `MalioDrawer`)
## Polish à venir (Tristan)
Tristan testera en navigateur et peaufinera : UX, classes Tailwind, animations, icônes, wording de toasts.
Les commits de polish suivront sur la même branche.
## Tests
- `npx nuxi typecheck` : net 0 nouvelle erreur (mêmes erreurs pré-existantes que sur `develop`, infrastructure auto-import) + 1 latente corrigée (AdminLinkSlug)
- `make nuxt-test` : 43/43 passent (0 régression)
- Tests manuels navigateur : voir cahier de test du ticket Lesstime #49
## Note pre-commit hook
Le hook a remonté un échec PHPUnit pré-existant sur `develop` (`CategoryDeleteTest::testPatchOnSoftDeletedReturns404` → 401 au lieu de 404, JWT non initialisé en test runner). Aucun PHP touché dans cette MR. Commit avec `--no-verify` autorisé par Tristan.
## Reviewer suggéré
Matthieu (back ↔ front + permissions).
---------
Co-authored-by: Matthieu <mtholot19@gmail.com>
Reviewed-on: #22
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>