From 4dcc24743628d13d262d0c1f62c9b21b5b3de215 Mon Sep 17 00:00:00 2001 From: tristan Date: Mon, 22 Jun 2026 16:13:30 +0200 Subject: [PATCH] =?UTF-8?q?feat(front)=20:=20branchement=20site=20courant?= =?UTF-8?q?=20+=20formats=20d'affichage=20des=20tickets=20de=20pes=C3=A9e?= =?UTF-8?q?=20(ERP-191)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/weighingTicketsIndex.spec.ts | 38 ++++++++++++-- .../pages/weighing-tickets/index.vue | 49 +++++++---------- .../__tests__/weighingTicketFormat.spec.ts | 52 +++++++++++++++++++ .../logistique/utils/weighingTicketFormat.ts | 46 ++++++++++++++++ 4 files changed, 152 insertions(+), 33 deletions(-) create mode 100644 frontend/modules/logistique/utils/__tests__/weighingTicketFormat.spec.ts create mode 100644 frontend/modules/logistique/utils/weighingTicketFormat.ts diff --git a/frontend/modules/logistique/pages/__tests__/weighingTicketsIndex.spec.ts b/frontend/modules/logistique/pages/__tests__/weighingTicketsIndex.spec.ts index ee5c2ea..708a940 100644 --- a/frontend/modules/logistique/pages/__tests__/weighingTicketsIndex.spec.ts +++ b/frontend/modules/logistique/pages/__tests__/weighingTicketsIndex.spec.ts @@ -1,5 +1,5 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { mount, flushPromises } from '@vue/test-utils' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { mount, flushPromises, type VueWrapper } from '@vue/test-utils' import { defineComponent, h, ref } from 'vue' // ── Auto-imports Nuxt stubbes globalement ─────────────────────────────────── @@ -9,6 +9,7 @@ const mockPush = vi.hoisted(() => vi.fn()) const mockApiGet = vi.hoisted(() => vi.fn()) const mockCan = vi.hoisted(() => vi.fn()) const mockFetch = vi.hoisted(() => vi.fn()) +const mockReset = vi.hoisted(() => vi.fn()) const mockToastError = vi.hoisted(() => vi.fn()) vi.stubGlobal('useI18n', () => ({ t: (key: string) => key })) @@ -17,6 +18,9 @@ vi.stubGlobal('useApi', () => ({ get: mockApiGet })) vi.stubGlobal('useRouter', () => ({ push: mockPush })) vi.stubGlobal('useToast', () => ({ error: mockToastError, success: vi.fn() })) vi.stubGlobal('usePermissions', () => ({ can: mockCan })) +// Site courant (switcher global) : ref pilotable pour simuler un changement de site. +const currentSiteRef = ref<{ id: number } | null>(null) +vi.stubGlobal('useCurrentSite', () => ({ currentSite: currentSiteRef })) // Le repository est lui aussi un auto-import : on controle les items renvoyes. // Contrepartie CLIENT (RG-5.03) → supplier / otherLabel absents (skip_null_values). @@ -40,6 +44,7 @@ vi.stubGlobal('useWeighingTicketsRepository', () => ({ goToPage: vi.fn(), setItemsPerPage: vi.fn(), setFilters: vi.fn(), + reset: mockReset, })) // happy-dom n'implemente pas createObjectURL : on ajoute les methodes statiques @@ -86,8 +91,13 @@ const PageHeaderStub = defineComponent({ setup(_, { slots }) { return () => h('div', {}, [slots.default?.(), slots.actions?.()]) }, }) +// Suivi des wrappers montés pour les démonter entre tests : sans cela, les +// watchers sur la ref module-level `currentSiteRef` (site courant) fuiteraient +// d'un test à l'autre et se déclencheraient en double. +const mountedWrappers: VueWrapper[] = [] + function mountPage() { - return mount(WeighingTicketsIndex, { + const wrapper = mount(WeighingTicketsIndex, { global: { stubs: { PageHeader: PageHeaderStub, @@ -96,6 +106,8 @@ function mountPage() { }, }, }) + mountedWrappers.push(wrapper) + return wrapper } describe('Liste des tickets de pesée (page /weighing-tickets)', () => { @@ -104,8 +116,17 @@ describe('Liste des tickets de pesée (page /weighing-tickets)', () => { mockApiGet.mockReset().mockResolvedValue(new Blob()) mockCan.mockReset().mockReturnValue(true) mockFetch.mockReset() + mockReset.mockReset() mockToastError.mockReset() capturedRows.value = [] + currentSiteRef.value = null + }) + + afterEach(() => { + // Démonte les composants montés → libère leurs watchers (site courant). + while (mountedWrappers.length > 0) { + mountedWrappers.pop()?.unmount() + } }) it('charge la liste au montage', async () => { @@ -114,6 +135,17 @@ describe('Liste des tickets de pesée (page /weighing-tickets)', () => { expect(mockFetch).toHaveBeenCalled() }) + it('recharge la liste (page 1) quand le site courant change', async () => { + mountPage() + await flushPromises() + expect(mockReset).not.toHaveBeenCalled() + + // Simule un switch de site via le switcher global. + currentSiteRef.value = { id: 2 } + await flushPromises() + expect(mockReset).toHaveBeenCalledTimes(1) + }) + it('formate la date au format JJ-MM-AAAA', async () => { const wrapper = mountPage() await flushPromises() diff --git a/frontend/modules/logistique/pages/weighing-tickets/index.vue b/frontend/modules/logistique/pages/weighing-tickets/index.vue index 1bea549..560ccf5 100644 --- a/frontend/modules/logistique/pages/weighing-tickets/index.vue +++ b/frontend/modules/logistique/pages/weighing-tickets/index.vue @@ -45,13 +45,18 @@ diff --git a/frontend/modules/logistique/utils/__tests__/weighingTicketFormat.spec.ts b/frontend/modules/logistique/utils/__tests__/weighingTicketFormat.spec.ts new file mode 100644 index 0000000..028994a --- /dev/null +++ b/frontend/modules/logistique/utils/__tests__/weighingTicketFormat.spec.ts @@ -0,0 +1,52 @@ +import { describe, it, expect } from 'vitest' +import { formatDateFr, formatWeightKg, formatPlate } from '../weighingTicketFormat' + +describe('weighingTicketFormat', () => { + // ── Date JJ-MM-AAAA ─────────────────────────────────────────────────────── + describe('formatDateFr', () => { + it('formate un datetime ISO en JJ-MM-AAAA', () => { + expect(formatDateFr('2026-06-17T09:12:00+02:00')).toBe('17-06-2026') + }) + + it('zéro-pad le jour et le mois', () => { + expect(formatDateFr('2026-01-05T00:00:00Z')).toBe('05-01-2026') + }) + + it('retourne une chaîne vide si absente ou invalide', () => { + expect(formatDateFr(null)).toBe('') + expect(formatDateFr(undefined)).toBe('') + expect(formatDateFr('pas-une-date')).toBe('') + }) + }) + + // ── Poids « X XXX Kg » ──────────────────────────────────────────────────── + describe('formatWeightKg', () => { + it('ajoute un séparateur de milliers (espace) et le suffixe Kg', () => { + expect(formatWeightKg(7150)).toBe('7 150 Kg') + expect(formatWeightKg(14300)).toBe('14 300 Kg') + expect(formatWeightKg(1000000)).toBe('1 000 000 Kg') + }) + + it('gère les petits nombres sans séparateur', () => { + expect(formatWeightKg(0)).toBe('0 Kg') + expect(formatWeightKg(999)).toBe('999 Kg') + }) + + it('retourne une chaîne vide si le poids est absent', () => { + expect(formatWeightKg(null)).toBe('') + expect(formatWeightKg(undefined)).toBe('') + }) + }) + + // ── Immatriculation UPPER ───────────────────────────────────────────────── + describe('formatPlate', () => { + it('met en majuscules et trim', () => { + expect(formatPlate(' ab-123-cd ')).toBe('AB-123-CD') + }) + + it('retourne une chaîne vide si absente', () => { + expect(formatPlate(null)).toBe('') + expect(formatPlate('')).toBe('') + }) + }) +}) diff --git a/frontend/modules/logistique/utils/weighingTicketFormat.ts b/frontend/modules/logistique/utils/weighingTicketFormat.ts new file mode 100644 index 0000000..25d022a --- /dev/null +++ b/frontend/modules/logistique/utils/weighingTicketFormat.ts @@ -0,0 +1,46 @@ +/** + * Filtres d'affichage du module « Tickets de pesée » (M5, ERP-191). Helpers PURS + * et testables, partagés par la liste et les écrans. Le serveur reste l'autorité + * de normalisation (spec-front § Règles de formatage) : ces helpers ne font que + * mettre en forme la valeur déjà normalisée renvoyée par l'API. + */ + +/** + * Date courte française `JJ-MM-AAAA` (spec M5). Chaîne vide si la valeur est + * absente ou invalide. Lit les composantes locales (cohérent avec l'affichage + * des autres répertoires M1→M4). + */ +export function formatDateFr(value: string | null | undefined): string { + if (!value) { + return '' + } + const date = new Date(value) + if (Number.isNaN(date.getTime())) { + return '' + } + const day = String(date.getDate()).padStart(2, '0') + const month = String(date.getMonth() + 1).padStart(2, '0') + return `${day}-${month}-${date.getFullYear()}` +} + +/** + * Poids en kg avec séparateur de milliers (espace) + suffixe « Kg » + * (spec-front : « 7 150 Kg »). Chaîne vide si le poids est absent (ticket dont la + * pesée à plein n'est pas finalisée). Groupement manuel (espace ASCII) pour un + * rendu déterministe, indépendant de l'ICU de l'environnement. + */ +export function formatWeightKg(value: number | null | undefined): string { + if (value === null || value === undefined) { + return '' + } + const grouped = String(Math.round(value)).replace(/\B(?=(\d{3})+(?!\d))/g, ' ') + return `${grouped} Kg` +} + +/** + * Immatriculation en MAJUSCULES (cohérent avec la normalisation serveur RG-5.01 : + * trim + UPPER). Chaîne vide si absente. + */ +export function formatPlate(value: string | null | undefined): string { + return value ? value.trim().toUpperCase() : '' +}