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 +}