From b82acdac01985e2f272075d1077bf54cd3579f23 Mon Sep 17 00:00:00 2001 From: tristan Date: Tue, 16 Jun 2026 06:12:19 +0000 Subject: [PATCH] =?UTF-8?q?fix(front)=20:=20aligner=20le=20filtre=20archiv?= =?UTF-8?q?es=20des=20r=C3=A9pertoires=20fournisseurs=20et=20prestataires?= =?UTF-8?q?=20sur=20client=20(ERP-173)=20(#110)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Contexte (ERP-173) Les répertoires **Fournisseurs** (M2) et **Prestataires** (M3) proposaient un filtre « Inclure les archivés » (affiche actifs **+** archivés, param `includeArchived`), alors que le répertoire **Client** — la référence — propose « Voir les archivés » (affiche les archivés **seuls**, param `archivedOnly`). ## Diagnostic Le back des 3 modules (providers, repositories, export controllers) est **déjà identique** : il gère `archivedOnly` (prioritaire). Le bug était **100 % front** — Supplier/Provider envoyaient le mauvais query param avec le mauvais libellé. ## Changement (front uniquement) - Libellé : « Inclure les archivés » → « **Voir les archivés** » - Query param : `includeArchived` → `archivedOnly` (case `filter-archived-only`, state `draft/appliedArchivedOnly`) - i18n `commercial.suppliers.filters` + `technique.providers.filters` - Tests Vitest alignés (suppliersIndex, useSuppliersRepository, useProvidersRepository) Aucune modif back nécessaire : la collection et l'export XLSX consomment déjà `archivedOnly`. ## Vérifications - `make nuxt-test` : 480/480 verts - ESLint : OK sur les fichiers touchés - Les 3 répertoires (Clients / Fournisseurs / Prestataires) ont désormais un filtre archives identique. Reviewed-on: https://gitea.malio.fr/MALIO-DEV/Starseed/pulls/110 Co-authored-by: tristan Co-committed-by: tristan --- frontend/i18n/locales/fr.json | 4 +-- .../__tests__/useSuppliersRepository.spec.ts | 6 ++--- .../pages/__tests__/suppliersIndex.spec.ts | 10 +++---- .../commercial/pages/suppliers/index.vue | 26 +++++++++---------- .../__tests__/useProvidersRepository.test.ts | 14 +++++----- .../composables/useProvidersRepository.ts | 9 ++++--- .../technique/pages/providers/index.vue | 26 +++++++++---------- 7 files changed, 48 insertions(+), 47 deletions(-) diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json index 7e1bb0f..17a09c7 100644 --- a/frontend/i18n/locales/fr.json +++ b/frontend/i18n/locales/fr.json @@ -70,7 +70,7 @@ "categories": "Catégories", "sites": "Sites", "status": "Statut", - "includeArchived": "Inclure les archivés", + "archivedOnly": "Voir les archivés", "apply": "Voir les résultats", "reset": "Réinitialiser" }, @@ -384,7 +384,7 @@ "categories": "Catégories", "sites": "Sites", "status": "Statut", - "includeArchived": "Inclure les archivés", + "archivedOnly": "Voir les archivés", "apply": "Voir les résultats", "reset": "Réinitialiser" }, diff --git a/frontend/modules/commercial/composables/__tests__/useSuppliersRepository.spec.ts b/frontend/modules/commercial/composables/__tests__/useSuppliersRepository.spec.ts index 6d5392c..a7a6f42 100644 --- a/frontend/modules/commercial/composables/__tests__/useSuppliersRepository.spec.ts +++ b/frontend/modules/commercial/composables/__tests__/useSuppliersRepository.spec.ts @@ -51,7 +51,7 @@ describe('useSuppliersRepository', () => { search: 'acme', 'categoryCode[]': ['NEGOCIANT', 'TRANSPORTEUR'], 'siteId[]': ['86', '17'], - includeArchived: true, + archivedOnly: true, }, { replace: true }, ) @@ -63,7 +63,7 @@ describe('useSuppliersRepository', () => { search: 'acme', 'categoryCode[]': ['NEGOCIANT', 'TRANSPORTEUR'], 'siteId[]': ['86', '17'], - includeArchived: true, + archivedOnly: true, page: 1, itemsPerPage: 10, }, @@ -73,7 +73,7 @@ describe('useSuppliersRepository', () => { it('repasse a une query propre apres reinitialisation des filtres', async () => { const repo = useSuppliersRepository() - await repo.setFilters({ search: 'acme', includeArchived: true }, { replace: true }) + await repo.setFilters({ search: 'acme', archivedOnly: true }, { replace: true }) await repo.setFilters({}, { replace: true }) expect(mockGet).toHaveBeenLastCalledWith( diff --git a/frontend/modules/commercial/pages/__tests__/suppliersIndex.spec.ts b/frontend/modules/commercial/pages/__tests__/suppliersIndex.spec.ts index 5ea48bc..5776303 100644 --- a/frontend/modules/commercial/pages/__tests__/suppliersIndex.spec.ts +++ b/frontend/modules/commercial/pages/__tests__/suppliersIndex.spec.ts @@ -172,16 +172,16 @@ describe('Répertoire fournisseurs (page /suppliers)', () => { ) }) - it('repercute le filtre « Inclure les archivés » dans setFilters sans toucher l\'URL', async () => { + it('repercute le filtre « Voir les archivés » dans setFilters sans toucher l\'URL', async () => { const wrapper = mountPage() await flushPromises() - // Coche « Inclure les archivés » puis applique les filtres. - await wrapper.find('input[data-id="filter-include-archived"]').setValue(true) + // Coche « Voir les archivés » puis applique les filtres. + await wrapper.find('input[data-id="filter-archived-only"]').setValue(true) await wrapper.find('[data-label="commercial.suppliers.filters.apply"]').trigger('click') expect(mockSetFilters).toHaveBeenLastCalledWith( - { includeArchived: true }, + { archivedOnly: true }, { replace: true }, ) // Etat 100 % local (regle n°6) : aucune navigation/query string declenchee. @@ -192,7 +192,7 @@ describe('Répertoire fournisseurs (page /suppliers)', () => { const wrapper = mountPage() await flushPromises() - await wrapper.find('input[data-id="filter-include-archived"]').setValue(true) + await wrapper.find('input[data-id="filter-archived-only"]').setValue(true) await wrapper.find('[data-label="commercial.suppliers.filters.apply"]').trigger('click') // Le libelle du bouton Filtrer porte le compteur (1 filtre actif). diff --git a/frontend/modules/commercial/pages/suppliers/index.vue b/frontend/modules/commercial/pages/suppliers/index.vue index ea374ef..182358a 100644 --- a/frontend/modules/commercial/pages/suppliers/index.vue +++ b/frontend/modules/commercial/pages/suppliers/index.vue @@ -128,13 +128,13 @@ - + @@ -254,12 +254,12 @@ const filterDrawerOpen = ref(false) const draftSearch = ref('') const draftCategoryCodes = ref([]) const draftSiteIds = ref([]) -const draftIncludeArchived = ref(false) +const draftArchivedOnly = ref(false) const appliedSearch = ref('') const appliedCategoryCodes = ref([]) const appliedSiteIds = ref([]) -const appliedIncludeArchived = ref(false) +const appliedArchivedOnly = ref(false) // Options des selects multi, chargees une fois (referentiels courts). const categoryOptions = ref([]) @@ -270,7 +270,7 @@ const activeFilterCount = computed(() => { if (appliedSearch.value.trim() !== '') count++ if (appliedCategoryCodes.value.length > 0) count++ if (appliedSiteIds.value.length > 0) count++ - if (appliedIncludeArchived.value) count++ + if (appliedArchivedOnly.value) count++ return count }) @@ -285,7 +285,7 @@ function openFilters(): void { draftSearch.value = appliedSearch.value draftCategoryCodes.value = [...appliedCategoryCodes.value] draftSiteIds.value = [...appliedSiteIds.value] - draftIncludeArchived.value = appliedIncludeArchived.value + draftArchivedOnly.value = appliedArchivedOnly.value filterDrawerOpen.value = true } @@ -311,7 +311,7 @@ function buildFilterPayload(): Record { if (appliedSearch.value.trim() !== '') payload.search = appliedSearch.value.trim() if (appliedCategoryCodes.value.length > 0) payload['categoryCode[]'] = [...appliedCategoryCodes.value] if (appliedSiteIds.value.length > 0) payload['siteId[]'] = [...appliedSiteIds.value] - if (appliedIncludeArchived.value) payload.includeArchived = true + if (appliedArchivedOnly.value) payload.archivedOnly = true return payload } @@ -321,7 +321,7 @@ function applyFilters(): void { appliedSearch.value = draftSearch.value.trim() appliedCategoryCodes.value = [...draftCategoryCodes.value] appliedSiteIds.value = [...draftSiteIds.value] - appliedIncludeArchived.value = draftIncludeArchived.value + appliedArchivedOnly.value = draftArchivedOnly.value setFilters(buildFilterPayload(), { replace: true }) filterDrawerOpen.value = false @@ -333,12 +333,12 @@ function resetFilters(): void { draftSearch.value = '' draftCategoryCodes.value = [] draftSiteIds.value = [] - draftIncludeArchived.value = false + draftArchivedOnly.value = false appliedSearch.value = '' appliedCategoryCodes.value = [] appliedSiteIds.value = [] - appliedIncludeArchived.value = false + appliedArchivedOnly.value = false setFilters({}, { replace: true }) } diff --git a/frontend/modules/technique/composables/__tests__/useProvidersRepository.test.ts b/frontend/modules/technique/composables/__tests__/useProvidersRepository.test.ts index 2bbf646..3e0a201 100644 --- a/frontend/modules/technique/composables/__tests__/useProvidersRepository.test.ts +++ b/frontend/modules/technique/composables/__tests__/useProvidersRepository.test.ts @@ -14,9 +14,9 @@ vi.stubGlobal('useApi', () => ({ get: mockApiGet })) * - l'enveloppe Hydra (member / totalItems) est consommee * - le header `Accept: application/ld+json` est envoye (sinon API Platform 4 * renvoie un tableau plat sans pagination) - * - EXCLUSION DES ARCHIVES PAR DEFAUT : aucun `includeArchived` n'est envoye + * - EXCLUSION DES ARCHIVES PAR DEFAUT : aucun `archivedOnly` n'est envoye * tant que l'utilisateur ne coche pas le filtre (le back masque alors les - * archives) ; le filtre `includeArchived` est bien transmis une fois applique. + * archives) ; le filtre `archivedOnly` est bien transmis une fois applique. */ describe('useProvidersRepository', () => { beforeEach(() => { @@ -53,26 +53,26 @@ describe('useProvidersRepository', () => { expect(repo.totalItems.value).toBe(1) }) - it('exclut les archives par defaut : aucun includeArchived au premier fetch', async () => { + it('exclut les archives par defaut : aucun archivedOnly au premier fetch', async () => { mockApiGet.mockResolvedValueOnce({ member: PAGE, totalItems: 1 }) const repo = useProvidersRepository() await repo.fetch() const query = mockApiGet.mock.calls[0][1] as Record - expect(query.includeArchived).toBeUndefined() + expect(query.archivedOnly).toBeUndefined() }) - it('transmet includeArchived une fois le filtre applique (retour page 1)', async () => { + it('transmet archivedOnly une fois le filtre applique (retour page 1)', async () => { mockApiGet.mockResolvedValueOnce({ member: PAGE, totalItems: 1 }) const repo = useProvidersRepository() await repo.fetch() mockApiGet.mockResolvedValueOnce({ member: PAGE, totalItems: 1 }) - await repo.setFilters({ includeArchived: true }) + await repo.setFilters({ archivedOnly: true }) expect(repo.currentPage.value).toBe(1) const query = mockApiGet.mock.calls.at(-1)?.[1] as Record - expect(query.includeArchived).toBe(true) + expect(query.archivedOnly).toBe(true) }) }) diff --git a/frontend/modules/technique/composables/useProvidersRepository.ts b/frontend/modules/technique/composables/useProvidersRepository.ts index de3ff19..eadb9e3 100644 --- a/frontend/modules/technique/composables/useProvidersRepository.ts +++ b/frontend/modules/technique/composables/useProvidersRepository.ts @@ -45,10 +45,11 @@ export interface Provider { * sur la ressource `/providers` (pagination serveur obligatoire ; jamais de * chargement integral en memoire). Miroir de `useSuppliersRepository` (M2). * - * Les filtres (recherche, categories, sites, inclusion des archives) sont pilotes - * par la page via `setFilters` du composable partage — la remise en page 1 est - * garantie. Par defaut, aucun `includeArchived` n'est envoye : le back masque - * donc les prestataires archives (exclusion par defaut, spec-back § 2.11). + * Les filtres (recherche, categories, sites, archives) sont pilotes par la page + * via `setFilters` du composable partage — la remise en page 1 est garantie. Par + * defaut, aucun `archivedOnly` n'est envoye : le back masque donc les prestataires + * archives (exclusion par defaut, spec-back § 2.11). Cocher « Voir les archivés » + * envoie `archivedOnly=true` → seules les archives sont listees (aligne sur Client). * * Le cloisonnement par site est applique AUTOMATIQUEMENT cote back (§ 2.13) en * fonction de l'utilisateur — rien a filtrer cote front. diff --git a/frontend/modules/technique/pages/providers/index.vue b/frontend/modules/technique/pages/providers/index.vue index cfedde4..ace7da8 100644 --- a/frontend/modules/technique/pages/providers/index.vue +++ b/frontend/modules/technique/pages/providers/index.vue @@ -129,13 +129,13 @@ - + @@ -258,12 +258,12 @@ const filterDrawerOpen = ref(false) const draftSearch = ref('') const draftCategoryCodes = ref([]) const draftSiteIds = ref([]) -const draftIncludeArchived = ref(false) +const draftArchivedOnly = ref(false) const appliedSearch = ref('') const appliedCategoryCodes = ref([]) const appliedSiteIds = ref([]) -const appliedIncludeArchived = ref(false) +const appliedArchivedOnly = ref(false) // Options des selects multi, chargees une fois (referentiels courts). const categoryOptions = ref([]) @@ -274,7 +274,7 @@ const activeFilterCount = computed(() => { if (appliedSearch.value.trim() !== '') count++ if (appliedCategoryCodes.value.length > 0) count++ if (appliedSiteIds.value.length > 0) count++ - if (appliedIncludeArchived.value) count++ + if (appliedArchivedOnly.value) count++ return count }) @@ -289,7 +289,7 @@ function openFilters(): void { draftSearch.value = appliedSearch.value draftCategoryCodes.value = [...appliedCategoryCodes.value] draftSiteIds.value = [...appliedSiteIds.value] - draftIncludeArchived.value = appliedIncludeArchived.value + draftArchivedOnly.value = appliedArchivedOnly.value filterDrawerOpen.value = true } @@ -315,7 +315,7 @@ function buildFilterPayload(): Record { if (appliedSearch.value.trim() !== '') payload.search = appliedSearch.value.trim() if (appliedCategoryCodes.value.length > 0) payload['categoryCode[]'] = [...appliedCategoryCodes.value] if (appliedSiteIds.value.length > 0) payload['siteId[]'] = [...appliedSiteIds.value] - if (appliedIncludeArchived.value) payload.includeArchived = true + if (appliedArchivedOnly.value) payload.archivedOnly = true return payload } @@ -325,7 +325,7 @@ function applyFilters(): void { appliedSearch.value = draftSearch.value.trim() appliedCategoryCodes.value = [...draftCategoryCodes.value] appliedSiteIds.value = [...draftSiteIds.value] - appliedIncludeArchived.value = draftIncludeArchived.value + appliedArchivedOnly.value = draftArchivedOnly.value setFilters(buildFilterPayload(), { replace: true }) filterDrawerOpen.value = false @@ -337,12 +337,12 @@ function resetFilters(): void { draftSearch.value = '' draftCategoryCodes.value = [] draftSiteIds.value = [] - draftIncludeArchived.value = false + draftArchivedOnly.value = false appliedSearch.value = '' appliedCategoryCodes.value = [] appliedSiteIds.value = [] - appliedIncludeArchived.value = false + appliedArchivedOnly.value = false setFilters({}, { replace: true }) }