ee1521384e
Auto Tag Develop / tag (push) Successful in 8s
## ERP-62 — Page Répertoire clients (datatable + Ajouter / Exporter) Tâche Lesstime #480. **Stacke sur ERP-61** (clés i18n `commercial.clients.*`) — non encore mergé : la diff vers `develop` inclut le commit ERP-61 tant qu'il n'est pas mergé. ### Front - Page `/clients` (route à plat) : `MalioDataTable` 6 colonnes (Nom entreprise / Contact / Téléphone formaté / Email / codes Catégories / badges Site(s)), toggle « Voir les archivés » (état 100 % local), boutons **+ Ajouter** (visible si `commercial.clients.manage`) et **Exporter** (visible si `view`, télécharge `clients/export.xlsx` via `useApi`), clic ligne → `/clients/{id}`, empty state. - Composable `useClientsRepository` = wrapper de `usePaginatedList<Client>({ url: '/clients' })` + toggle `includeArchived` (repasse page 1). - Util `formatPhoneFR` (signature cible à coordonner avec ERP-66 / 1.13) + clé i18n `showArchived`. ### Back — ⚠️ MAJ contrat de sérialisation (incluse dans cette MR) Le `GET /api/clients` n'exposait ni les codes catégories ni les sites en liste (le bloc Lesstime l'affirmait à tort). Corrigé : - `Client` : `category:read` + `site:read` ajoutés aux `normalizationContext` (GetCollection/Get/Post/Patch) + accesseur agrégé `getSites()` (`#[Groups(client:read)]`). - `DoctrineClientRepository::createListQueryBuilder` : jointures + `addSelect` (categories / addresses / sites) anti N+1. - Aucune migration (pure sérialisation). ### Tests - Back : `ClientApiTest` (codes catégories + sites name/color en liste). `make test` ✅ 454. - Front : `useClientsRepository.spec.ts` + `phone.test.ts`. `vitest` ✅ 111. `nuxi typecheck` ✅ (mes fichiers). ### Non couvert Golden path navigateur non joué : dev-nuxt (conteneur) cassé (résolution `@malio/layer-ui/tailwind.config.ts`) + BDD sans clients démo (nécessite `make db-reset`). Aspects front restants traités séparément. --------- Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #44 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import type { HydraCollection } from '~/shared/utils/api'
|
|
import type { Client } from '../useClientsRepository'
|
|
|
|
// `useApi` est un auto-import Nuxt : on le stubbe globalement pour intercepter
|
|
// les appels declenches par usePaginatedList (que useClientsRepository enveloppe)
|
|
// et controler les reponses. Meme pattern que useCategoriesAdmin.spec.ts.
|
|
const mockGet = vi.hoisted(() => vi.fn())
|
|
vi.stubGlobal('useApi', () => ({
|
|
get: mockGet,
|
|
post: vi.fn(),
|
|
put: vi.fn(),
|
|
patch: vi.fn(),
|
|
delete: vi.fn(),
|
|
}))
|
|
|
|
// Import APRES le stub pour que useApi soit bien resolu au top-level du module.
|
|
const { useClientsRepository } = await import('../useClientsRepository')
|
|
|
|
/** Envelope Hydra minimale (la liste reelle des membres importe peu ici). */
|
|
function makeHydra(total: number): HydraCollection<Client> {
|
|
return { totalItems: total, member: [] }
|
|
}
|
|
|
|
describe('useClientsRepository', () => {
|
|
beforeEach(() => {
|
|
mockGet.mockReset()
|
|
// 25 items → 3 pages a 10/page : permet de tester la navigation page 2.
|
|
mockGet.mockResolvedValue(makeHydra(25))
|
|
})
|
|
|
|
it('cible la ressource /clients en page 1 par defaut', async () => {
|
|
const repo = useClientsRepository()
|
|
await repo.fetch()
|
|
|
|
expect(mockGet).toHaveBeenLastCalledWith(
|
|
'/clients',
|
|
{ page: 1, itemsPerPage: 10 },
|
|
expect.objectContaining({ toast: false }),
|
|
)
|
|
})
|
|
|
|
it('pousse les filtres du drawer (categories multi, sites, archives) et retombe en page 1', async () => {
|
|
const repo = useClientsRepository()
|
|
await repo.fetch()
|
|
await repo.goToPage(2)
|
|
expect(repo.currentPage.value).toBe(2)
|
|
|
|
await repo.setFilters(
|
|
{
|
|
search: 'acme',
|
|
'categoryCode[]': ['DISTRIBUTEUR', 'COURTIER'],
|
|
'siteId[]': ['1', '2'],
|
|
archivedOnly: true,
|
|
},
|
|
{ replace: true },
|
|
)
|
|
|
|
expect(repo.currentPage.value).toBe(1)
|
|
expect(mockGet).toHaveBeenLastCalledWith(
|
|
'/clients',
|
|
{
|
|
search: 'acme',
|
|
'categoryCode[]': ['DISTRIBUTEUR', 'COURTIER'],
|
|
'siteId[]': ['1', '2'],
|
|
archivedOnly: true,
|
|
page: 1,
|
|
itemsPerPage: 10,
|
|
},
|
|
expect.objectContaining({ toast: false }),
|
|
)
|
|
})
|
|
|
|
it('repasse a une query propre apres reinitialisation des filtres', async () => {
|
|
const repo = useClientsRepository()
|
|
await repo.setFilters({ search: 'acme', archivedOnly: true }, { replace: true })
|
|
await repo.setFilters({}, { replace: true })
|
|
|
|
expect(mockGet).toHaveBeenLastCalledWith(
|
|
'/clients',
|
|
{ page: 1, itemsPerPage: 10 },
|
|
expect.objectContaining({ toast: false }),
|
|
)
|
|
})
|
|
})
|