feat(catalog) : categories multi-types (M:N) + filtres liste
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Failing after 51s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m9s

Volet A — relation Category <-> CategoryType passee de ManyToOne a ManyToMany
(jonction category_category_type). Au moins un type obligatoire (Assert\Count),
unicite du nom desormais GLOBALE parmi les actifs (uq_category_name_active).
Migration avec backfill + drop de l'ancienne colonne. Contrat Shared
CategoryInterface : getCategoryTypeCode() -> getCategoryTypeCodes(): array ;
RG-2.10 fournisseurs (Supplier / SupplierAddress / fixtures) revalident
« contient FOURNISSEUR ». Provider/Repository : filtre type via sous-requete
EXISTS (sans tronquer la collection embarquee), eager-load anti-N+1.

Volet B — bouton « Filtres » sur la liste des categories (recherche nom +
types multi en OR), sur le modele du Repertoire Clients ; etat local, jamais
persiste en URL. Filtres back ?name= et ?typeId[]= sur la collection.

Front : multi-select MalioSelectCheckbox, useCategoryForm en categoryTypeIds[],
colonne « Types », i18n. ColumnCommentsCatalog + makefile test-db-setup alignes
sur le nouvel index partiel. Tests Catalog/Commercial adaptes + CategoryFilterTest.
This commit is contained in:
Matthieu
2026-06-08 11:26:07 +02:00
parent 43b2251ef1
commit f813c1732e
31 changed files with 909 additions and 257 deletions
@@ -24,16 +24,18 @@
required
/>
<!-- Type (RG-1.05 obligatoire). MalioSelect porte la valeur en
number (categoryType id) ; conversion en IRI au moment du save
par le composable useCategoryForm. -->
<MalioSelect
v-model="form.categoryTypeId.value"
<!-- Types (RG-1.05 : au moins un obligatoire). MalioSelectCheckbox
porte un tableau d'ids (categoryType id) ; conversion en tableau
d'IRI au moment du save par le composable useCategoryForm. -->
<MalioSelectCheckbox
v-model="form.categoryTypeIds.value"
:options="typeOptions"
:label="t('admin.categories.form.type')"
:empty-option-label="t('admin.categories.form.typePlaceholder')"
:error="form.errors.categoryType"
:label="t('admin.categories.form.types')"
:empty-option-label="t('admin.categories.form.typesPlaceholder')"
:error="form.errors.categoryTypes"
:display-tag="true"
:disabled="loadingTypes"
required
/>
</form>