Compare commits

..

27 Commits

Author SHA1 Message Date
tristan b6b5bb06e8 fix(transport) : affiche le message 409 (homonyme) à la restauration + virgule décimale dans sanitizeDecimal (ERP-170)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m7s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m41s
2026-06-17 16:08:02 +02:00
tristan c371057c0b style(transport) : tableau prix — réduit Adresse sites au profit d'Adresse livraisons (ERP-170) 2026-06-17 16:08:02 +02:00
tristan 5125883e21 fix(transport) : tableau prix — corrige l'inversion Adresse sites / Adresse livraisons (ERP-170) 2026-06-17 16:08:02 +02:00
tristan 5bbd4ddb47 style(transport) : tableau prix — libellés colonnes + élargit Transporteurs/Adresse livraisons, réduit Forfait/Tonne/Indexation/État (ERP-170) 2026-06-17 16:08:02 +02:00
tristan 20296ac149 style(transport) : datatable qualimat table-fixed (radio étroit, colonnes égales) + icônes onglets prix/qualimat (ERP-170) 2026-06-17 16:08:02 +02:00
tristan fe1d012548 style(transport) : tableau prix consultation en table-fixed (colonnes à parts égales, contenant étroit) (ERP-170) 2026-06-17 16:08:02 +02:00
tristan d86dc69cf2 feat(transport) : consultation — contenant en radios lecture seule (aligné ajout/modif) (ERP-170) 2026-06-17 16:08:02 +02:00
tristan 07ed57f283 feat(transport) : contenant du formulaire principal en radios centrés (Benne par défaut) (ERP-170) 2026-06-17 16:08:02 +02:00
tristan b5749520bc fix(transport) : consultation — disposition du bloc principal alignée sur l'ajout (LIOT, décharge col 3, affréter col 4) (ERP-170) 2026-06-17 16:08:02 +02:00
tristan 02d2fde653 fix(transport) : indexation réellement plafonnée à 100 % — re-synchronise le champ amount contrôlé via :key (ERP-170) 2026-06-17 16:08:02 +02:00
tristan 0d284fe488 fix(transport) : volume m³ en champ texte décimal + indexation en montant % plafonné à 100 (ERP-170) 2026-06-17 16:08:02 +02:00
tristan 48ca963a9d fix(transport) : tableau prix — en-tête « Contenant » sur la colonne de groupe (ERP-170) 2026-06-17 16:08:02 +02:00
tristan b11968f5e5 fix(transport) : tableau prix — supprime la double bordure du bas + séparateur épais entre groupes Benne/Fond mouvant (ERP-170) 2026-06-17 16:08:02 +02:00
tristan 5109b5f57a fix(transport) : tableau prix consultation — police/bordures/radius alignés MalioDataTable, colonne groupe en épine (ERP-170) 2026-06-17 16:08:02 +02:00
tristan d5a01ac85f fix(transport) : name de radio unique par bloc prix (useId) — plus de groupe partagé entre blocs (ERP-170) 2026-06-17 16:08:02 +02:00
tristan 7adf3a511a fix(transport) : tableau prix consultation — cellule de groupe fusionnée (Fond Mouvant/Benne) + colonnes maquette (ERP-170) 2026-06-17 16:08:02 +02:00
tristan e612eae391 feat(transport) : consultation + modification transporteur (ERP-170) 2026-06-17 16:08:02 +02:00
tristan fb9c15c52a fix(transport) : pré-validation front du bloc prix — erreurs inline sous tous les champs requis (selects branche) (ERP-169)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m4s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m34s
2026-06-17 16:08:02 +02:00
tristan e1712465f1 fix(transport) : bloc prix — radios sens/contenant/tarif horizontaux et centrés (h-12) en colonne 1 (ERP-169) 2026-06-17 16:08:02 +02:00
tristan 6ff5b13ce2 fix(transport) : bloc prix par défaut (CLIENT), sens seul en ligne 1, payload omet scalaires vides (422 inline au lieu de 400) (ERP-169) 2026-06-17 16:08:02 +02:00
tristan a26bb09ee1 fix(transport) : bloc prix — radios sans label de groupe, sens en colonne 1, défaut Benne/Forfait (ERP-169) 2026-06-17 16:08:02 +02:00
tristan 07e0bcbcce feat(transport) : onglet prix transporteur (ERP-169) 2026-06-17 16:08:02 +02:00
tristan f29266e5e8 fix(transport) : contact transporteur valide si prénom OU nom (alignement M1/M2/M3) (ERP-168)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m24s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m35s
2026-06-17 16:07:51 +02:00
tristan f27db02cb6 fix(transport) : règle « + Nouveau contact » alignée sur M1/M2/M3 (prénom OU nom) (ERP-168) 2026-06-17 16:07:51 +02:00
tristan 5765ba7178 feat(transport) : onglet contacts transporteur (ERP-168) 2026-06-17 16:07:51 +02:00
tristan ef996c3672 feat(transport) : onglet adresses transporteur (ERP-167)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m18s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m34s
2026-06-17 16:06:56 +02:00
tristan c6259a96cd fix(transport) : intégration QUALIMAT — copie locale seulement après PATCH réussi (évite un état non persisté) (ERP-166)
Pull Request — Quality gate / Backend (PHP CS + PHPUnit) (pull_request) Successful in 3m4s
Pull Request — Quality gate / Frontend (lint + Vitest + build) (pull_request) Successful in 1m43s
2026-06-17 16:01:45 +02:00
5 changed files with 50 additions and 24 deletions
@@ -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.