Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b6b5bb06e8 | |||
| c371057c0b | |||
| 5125883e21 | |||
| 5bbd4ddb47 | |||
| 20296ac149 | |||
| fe1d012548 | |||
| d86dc69cf2 | |||
| 07ed57f283 | |||
| b5749520bc | |||
| 02d2fde653 | |||
| 0d284fe488 | |||
| 48ca963a9d | |||
| b11968f5e5 | |||
| 5109b5f57a | |||
| d5a01ac85f | |||
| 7adf3a511a | |||
| e612eae391 | |||
| fb9c15c52a | |||
| e1712465f1 | |||
| 6ff5b13ce2 | |||
| a26bb09ee1 | |||
| 07e0bcbcce | |||
| f29266e5e8 | |||
| f27db02cb6 | |||
| 5765ba7178 | |||
| ef996c3672 | |||
| c6259a96cd |
@@ -412,6 +412,23 @@ describe('useCarrierForm — copie QUALIMAT (ERP-166)', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('après création : PATCH en échec → pas de copie locale (rollback) et retour false', async () => {
|
||||
mockPost.mockResolvedValueOnce({ id: 9, name: 'X', certificationType: 'GMP_PLUS' })
|
||||
mockPatch.mockRejectedValueOnce({ response: { status: 500, _data: {} } })
|
||||
const form = useCarrierForm()
|
||||
form.main.name = 'X'
|
||||
form.main.certificationType = 'GMP_PLUS'
|
||||
await form.submitMain()
|
||||
|
||||
const ok = await form.applyQualimatSelection(QUALIMAT_ROW)
|
||||
|
||||
// Échec serveur : l'UI ne doit pas refléter une intégration QUALIMAT non persistée.
|
||||
expect(ok).toBe(false)
|
||||
expect(form.main.name).toBe('X')
|
||||
expect(form.main.certificationType).toBe('GMP_PLUS')
|
||||
expect(form.main.qualimatCarrierIri).toBeNull()
|
||||
})
|
||||
|
||||
it('buildMainPayload inclut qualimatCarrier + certificationType QUALIMAT après intégration', async () => {
|
||||
const form = useCarrierForm()
|
||||
form.main.name = 'Acme'
|
||||
|
||||
@@ -659,10 +659,27 @@ export function useCarrierForm() {
|
||||
* § 2.5) : copie le nom, force la certification à « QUALIMAT » (lecture seule),
|
||||
* pose la FK `qualimatCarrier` (IRI) et copie l'adresse (pour l'onglet Adresses).
|
||||
* Si le transporteur existe déjà (post-POST, cas nominal de l'onglet), persiste
|
||||
* la copie via un PATCH partiel `carrier:write:main`. La copie locale a lieu
|
||||
* dans tous les cas. Retourne true si l'intégration a abouti.
|
||||
* d'abord la copie via un PATCH partiel `carrier:write:main` : la copie locale
|
||||
* (nom, certification figée « QUALIMAT », FK, adresse) n'est appliquée qu'en cas
|
||||
* de succès, pour ne pas laisser l'UI dans un état QUALIMAT non sauvegardé si le
|
||||
* PATCH échoue. Retourne true si l'intégration a abouti.
|
||||
*/
|
||||
async function applyQualimatSelection(row: QualimatCarrierRow): Promise<boolean> {
|
||||
// Transporteur déjà créé : on persiste avant de refléter localement.
|
||||
if (carrierId.value !== null) {
|
||||
try {
|
||||
await patchCarrier({
|
||||
qualimatCarrier: row['@id'],
|
||||
name: row.name,
|
||||
certificationType: 'QUALIMAT',
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
mainErrors.handleApiError(error, { fallbackMessage: t('transport.carriers.toast.error') })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
main.name = row.name ?? ''
|
||||
main.certificationType = 'QUALIMAT'
|
||||
main.qualimatCarrierIri = row['@id']
|
||||
@@ -682,23 +699,7 @@ export function useCarrierForm() {
|
||||
street: row.address || null,
|
||||
streetComplement: null,
|
||||
}]
|
||||
|
||||
if (carrierId.value === null) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
await patchCarrier({
|
||||
qualimatCarrier: row['@id'],
|
||||
name: row.name,
|
||||
certificationType: 'QUALIMAT',
|
||||
})
|
||||
return true
|
||||
}
|
||||
catch (error) {
|
||||
mainErrors.handleApiError(error, { fallbackMessage: t('transport.carriers.toast.error') })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -255,6 +255,7 @@ import {
|
||||
showRestoreAction,
|
||||
type CarrierPriceRead,
|
||||
} from '~/modules/transport/utils/forms/carrierMappers'
|
||||
import { extractApiErrorMessage } from '~/shared/utils/api'
|
||||
|
||||
interface SelectOption {
|
||||
value: string
|
||||
@@ -481,8 +482,14 @@ async function runToggleArchive(): Promise<void> {
|
||||
: t('transport.carriers.toast.restoreSuccess'),
|
||||
})
|
||||
}
|
||||
catch {
|
||||
toast.error({ title: t('transport.carriers.toast.error') })
|
||||
catch (err) {
|
||||
// Surface le message back (ex. 409 « homonyme actif » à la restauration),
|
||||
// propagé exprès par useCarrier ; fallback générique sinon.
|
||||
const data = (err as { response?: { _data?: unknown } })?.response?._data
|
||||
toast.error({
|
||||
title: t('transport.carriers.toast.error'),
|
||||
message: extractApiErrorMessage(data) || undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ 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('30,5 kg')).toBe('30.5') // virgule FR → point ; 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('')
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* « nombres avec des points » comme les autres modules). La virgule décimale FR est
|
||||
* convertie en point (« 30,5 » → « 30.5 ») ; tout autre caractère est supprimé.
|
||||
*/
|
||||
export function sanitizeDecimal(value: string): string {
|
||||
let cleaned = (value ?? '').replace(/[^0-9.]/g, '')
|
||||
let cleaned = (value ?? '').replace(/,/g, '.').replace(/[^0-9.]/g, '')
|
||||
const dot = cleaned.indexOf('.')
|
||||
if (dot !== -1) {
|
||||
// Conserve le 1er point, retire les suivants.
|
||||
|
||||
Reference in New Issue
Block a user