From 0d284fe488307430a4edcf8be62fafa8796ffe20 Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 17 Jun 2026 14:11:24 +0200 Subject: [PATCH] =?UTF-8?q?fix(transport)=20:=20volume=20m=C2=B3=20en=20ch?= =?UTF-8?q?amp=20texte=20d=C3=A9cimal=20+=20indexation=20en=20montant=20%?= =?UTF-8?q?=20plafonn=C3=A9=20=C3=A0=20100=20(ERP-170)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/pages/carriers/[id]/edit.vue | 19 +++++++++++-- .../modules/transport/pages/carriers/new.vue | 15 ++++++++--- .../utils/forms/__tests__/numberInput.test.ts | 22 +++++++++++++++ .../transport/utils/forms/numberInput.ts | 27 +++++++++++++++++++ 4 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 frontend/modules/transport/utils/forms/__tests__/numberInput.test.ts create mode 100644 frontend/modules/transport/utils/forms/numberInput.ts diff --git a/frontend/modules/transport/pages/carriers/[id]/edit.vue b/frontend/modules/transport/pages/carriers/[id]/edit.vue index 2857975..3f3d9ce 100644 --- a/frontend/modules/transport/pages/carriers/[id]/edit.vue +++ b/frontend/modules/transport/pages/carriers/[id]/edit.vue @@ -63,7 +63,15 @@ /> @@ -173,6 +187,7 @@ import CarrierContactBlock from '~/modules/transport/components/CarrierContactBl import CarrierPriceBlock from '~/modules/transport/components/CarrierPriceBlock.vue' import { useCarrierForm } from '~/modules/transport/composables/useCarrierForm' import { useCarrier } from '~/modules/transport/composables/useCarrier' +import { clampPercent, sanitizeDecimal } from '~/modules/transport/utils/forms/numberInput' interface SelectOption { value: string diff --git a/frontend/modules/transport/pages/carriers/new.vue b/frontend/modules/transport/pages/carriers/new.vue index afdd702..130f54e 100644 --- a/frontend/modules/transport/pages/carriers/new.vue +++ b/frontend/modules/transport/pages/carriers/new.vue @@ -86,12 +86,16 @@ « Affreter ». La ligne 1 étant pleine (4 colonnes), ils démarrent naturellement en colonne 1 de la ligne 2. --> @@ -344,6 +350,7 @@ import CarrierContactBlock from '~/modules/transport/components/CarrierContactBl import CarrierPriceBlock from '~/modules/transport/components/CarrierPriceBlock.vue' import { useCarrierForm } from '~/modules/transport/composables/useCarrierForm' import { useQualimatSearch, type QualimatCarrierRow } from '~/modules/transport/composables/useQualimatSearch' +import { clampPercent, sanitizeDecimal } from '~/modules/transport/utils/forms/numberInput' interface SelectOption { value: string diff --git a/frontend/modules/transport/utils/forms/__tests__/numberInput.test.ts b/frontend/modules/transport/utils/forms/__tests__/numberInput.test.ts new file mode 100644 index 0000000..e64a7df --- /dev/null +++ b/frontend/modules/transport/utils/forms/__tests__/numberInput.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest' +import { clampPercent, sanitizeDecimal } from '../numberInput' + +describe('numberInput — saisie volume / indexation (ERP-170)', () => { + it('sanitizeDecimal : ne garde que chiffres + un seul point', () => { + expect(sanitizeDecimal('30')).toBe('30') + expect(sanitizeDecimal('30.5')).toBe('30.5') + expect(sanitizeDecimal('30,5 kg')).toBe('305') // virgule + espace + lettres retirés + expect(sanitizeDecimal('1.2.3')).toBe('1.23') // un seul point conservé + expect(sanitizeDecimal('abc12.3x')).toBe('12.3') + expect(sanitizeDecimal('')).toBe('') + }) + + it('clampPercent : plafonne à 100, laisse le reste tel quel', () => { + expect(clampPercent('50')).toBe('50') + expect(clampPercent('100')).toBe('100') + expect(clampPercent('150')).toBe('100') + expect(clampPercent('100.01')).toBe('100') + expect(clampPercent('12,5')).toBe('12,5') // ≤ 100 → inchangé + expect(clampPercent('')).toBe('') + }) +}) diff --git a/frontend/modules/transport/utils/forms/numberInput.ts b/frontend/modules/transport/utils/forms/numberInput.ts new file mode 100644 index 0000000..6e845c7 --- /dev/null +++ b/frontend/modules/transport/utils/forms/numberInput.ts @@ -0,0 +1,27 @@ +/** + * Helpers de saisie numérique du formulaire principal transporteur (ERP-170). + * Champs texte restreints (volume m³ décimal, indexation plafonnée). Purs / testables. + */ + +/** + * Restreint une saisie à un nombre décimal : chiffres + UN seul point (RG volume m³, + * « nombres avec des points » comme les autres modules). Supprime tout autre caractère. + */ +export function sanitizeDecimal(value: string): string { + let cleaned = (value ?? '').replace(/[^0-9.]/g, '') + const dot = cleaned.indexOf('.') + if (dot !== -1) { + // Conserve le 1er point, retire les suivants. + cleaned = cleaned.slice(0, dot + 1) + cleaned.slice(dot + 1).replace(/\./g, '') + } + return cleaned +} + +/** + * Plafonne un pourcentage à 100 (contrainte FRONT : l'indexation n'a pas de max back). + * Renvoie « 100 » si la valeur saisie dépasse 100, sinon la valeur telle quelle. + */ +export function clampPercent(value: string): string { + const n = Number(String(value ?? '').replace(',', '.').replace(/\s/g, '')) + return (!Number.isNaN(n) && n > 100) ? '100' : value +}