fix(transport) : volume m³ en champ texte décimal + indexation en montant % plafonné à 100 (ERP-170)
This commit is contained in:
@@ -63,7 +63,15 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<template v-if="showCharteredFields">
|
<template v-if="showCharteredFields">
|
||||||
<MalioInputNumber v-model="main.indexationRate" :label="t('transport.carriers.form.main.indexationRate')" :required="true" :error="mainErrors.errors.indexationRate" />
|
<MalioInputAmount
|
||||||
|
:model-value="main.indexationRate"
|
||||||
|
:label="t('transport.carriers.form.main.indexationRate')"
|
||||||
|
icon-name="mdi:percent"
|
||||||
|
icon-position="right"
|
||||||
|
:required="true"
|
||||||
|
:error="mainErrors.errors.indexationRate"
|
||||||
|
@update:model-value="(v: string) => main.indexationRate = clampPercent(v)"
|
||||||
|
/>
|
||||||
<MalioSelect
|
<MalioSelect
|
||||||
:model-value="main.containerType"
|
:model-value="main.containerType"
|
||||||
:options="containerOptions"
|
:options="containerOptions"
|
||||||
@@ -73,7 +81,13 @@
|
|||||||
:error="mainErrors.errors.containerType"
|
:error="mainErrors.errors.containerType"
|
||||||
@update:model-value="(v: string | number | null) => main.containerType = v === null ? null : String(v)"
|
@update:model-value="(v: string | number | null) => main.containerType = v === null ? null : String(v)"
|
||||||
/>
|
/>
|
||||||
<MalioInputNumber v-model="main.volumeM3" :label="t('transport.carriers.form.main.volumeM3')" :required="true" :error="mainErrors.errors.volumeM3" />
|
<MalioInputText
|
||||||
|
:model-value="main.volumeM3"
|
||||||
|
:label="t('transport.carriers.form.main.volumeM3')"
|
||||||
|
:required="true"
|
||||||
|
:error="mainErrors.errors.volumeM3"
|
||||||
|
@update:model-value="(v: string) => main.volumeM3 = sanitizeDecimal(v)"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -173,6 +187,7 @@ import CarrierContactBlock from '~/modules/transport/components/CarrierContactBl
|
|||||||
import CarrierPriceBlock from '~/modules/transport/components/CarrierPriceBlock.vue'
|
import CarrierPriceBlock from '~/modules/transport/components/CarrierPriceBlock.vue'
|
||||||
import { useCarrierForm } from '~/modules/transport/composables/useCarrierForm'
|
import { useCarrierForm } from '~/modules/transport/composables/useCarrierForm'
|
||||||
import { useCarrier } from '~/modules/transport/composables/useCarrier'
|
import { useCarrier } from '~/modules/transport/composables/useCarrier'
|
||||||
|
import { clampPercent, sanitizeDecimal } from '~/modules/transport/utils/forms/numberInput'
|
||||||
|
|
||||||
interface SelectOption {
|
interface SelectOption {
|
||||||
value: string
|
value: string
|
||||||
|
|||||||
@@ -86,12 +86,16 @@
|
|||||||
« Affreter ». La ligne 1 étant pleine (4 colonnes), ils démarrent
|
« Affreter ». La ligne 1 étant pleine (4 colonnes), ils démarrent
|
||||||
naturellement en colonne 1 de la ligne 2. -->
|
naturellement en colonne 1 de la ligne 2. -->
|
||||||
<template v-if="showCharteredFields">
|
<template v-if="showCharteredFields">
|
||||||
<MalioInputNumber
|
<!-- Indexation : montant en % (icône à droite), plafonné à 100. -->
|
||||||
v-model="main.indexationRate"
|
<MalioInputAmount
|
||||||
|
:model-value="main.indexationRate"
|
||||||
:label="t('transport.carriers.form.main.indexationRate')"
|
:label="t('transport.carriers.form.main.indexationRate')"
|
||||||
|
icon-name="mdi:percent"
|
||||||
|
icon-position="right"
|
||||||
:required="true"
|
:required="true"
|
||||||
:readonly="mainLocked"
|
:readonly="mainLocked"
|
||||||
:error="mainErrors.errors.indexationRate"
|
:error="mainErrors.errors.indexationRate"
|
||||||
|
@update:model-value="(v: string) => main.indexationRate = clampPercent(v)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Contenant : Benne / Fond mouvant (RG-4.03). -->
|
<!-- Contenant : Benne / Fond mouvant (RG-4.03). -->
|
||||||
@@ -106,12 +110,14 @@
|
|||||||
@update:model-value="(v: string | number | null) => main.containerType = v === null ? null : String(v)"
|
@update:model-value="(v: string | number | null) => main.containerType = v === null ? null : String(v)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MalioInputNumber
|
<!-- Volume m³ : champ texte restreint aux nombres à décimales (point). -->
|
||||||
v-model="main.volumeM3"
|
<MalioInputText
|
||||||
|
:model-value="main.volumeM3"
|
||||||
:label="t('transport.carriers.form.main.volumeM3')"
|
:label="t('transport.carriers.form.main.volumeM3')"
|
||||||
:required="true"
|
:required="true"
|
||||||
:readonly="mainLocked"
|
:readonly="mainLocked"
|
||||||
:error="mainErrors.errors.volumeM3"
|
:error="mainErrors.errors.volumeM3"
|
||||||
|
@update:model-value="(v: string) => main.volumeM3 = sanitizeDecimal(v)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
@@ -344,6 +350,7 @@ import CarrierContactBlock from '~/modules/transport/components/CarrierContactBl
|
|||||||
import CarrierPriceBlock from '~/modules/transport/components/CarrierPriceBlock.vue'
|
import CarrierPriceBlock from '~/modules/transport/components/CarrierPriceBlock.vue'
|
||||||
import { useCarrierForm } from '~/modules/transport/composables/useCarrierForm'
|
import { useCarrierForm } from '~/modules/transport/composables/useCarrierForm'
|
||||||
import { useQualimatSearch, type QualimatCarrierRow } from '~/modules/transport/composables/useQualimatSearch'
|
import { useQualimatSearch, type QualimatCarrierRow } from '~/modules/transport/composables/useQualimatSearch'
|
||||||
|
import { clampPercent, sanitizeDecimal } from '~/modules/transport/utils/forms/numberInput'
|
||||||
|
|
||||||
interface SelectOption {
|
interface SelectOption {
|
||||||
value: string
|
value: string
|
||||||
|
|||||||
@@ -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('')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user