fix(front) : ajustements du formulaire ticket de pesée (ERP-189/190)
- Poids/DSD en champs texte verrouillés sur les chiffres et désactivés. - Boutons de pesée : icône mdi:weight à gauche + gap-8. - Bloc « Poids à vide » réagencé en 3 lignes (contrepartie / Date-Poids-DSD-Immat / Tout format). - Omission des clés null dans les payloads (compact) : requis vides → message NotBlank métier au lieu d'une erreur de type. - Pesée obligatoire (RG-5.07) signalée inline sous Poids/DSD ; toutes les violations affichées d'un seul aller-retour. - Erreur d'immatriculation affichée uniquement sur le bloc « Poids à vide » (plus de doublon sur le bloc plein).
This commit is contained in:
@@ -15,6 +15,31 @@ describe('useWeighingTicketForm', () => {
|
||||
expect(form.counterpartyType.value).toBeNull()
|
||||
})
|
||||
|
||||
// ── Omission des requis vides (compact) ──────────────────────────────────
|
||||
it('buildCreatePayload omet les clés null (requis vides absents, pas envoyés à null)', () => {
|
||||
const form = useWeighingTicketForm()
|
||||
// Formulaire vierge : counterpartyType / immatriculation non remplis.
|
||||
const payload = form.buildCreatePayload()
|
||||
// Absents (et non null) → le back applique NotBlank (message métier) plutôt
|
||||
// qu'une erreur de type opaque (« doit être de type string »).
|
||||
expect(payload).not.toHaveProperty('counterpartyType')
|
||||
expect(payload).not.toHaveProperty('immatriculation')
|
||||
expect(payload).not.toHaveProperty('emptyWeight')
|
||||
// Les non-null restent : date du jour + booléen Tout format.
|
||||
expect(payload.emptyDate).toBe('2026-06-22')
|
||||
expect(payload.plateFreeFormat).toBe(false)
|
||||
})
|
||||
|
||||
// ── Pesée obligatoire front-only (RG-5.07) ───────────────────────────────
|
||||
it('missingWeighingFields liste Poids/DSD manquants, puis vide après pesée', () => {
|
||||
const form = useWeighingTicketForm()
|
||||
expect(form.missingWeighingFields('empty')).toEqual(['emptyWeight', 'emptyDsd'])
|
||||
expect(form.missingWeighingFields('full')).toEqual(['fullWeight', 'fullDsd'])
|
||||
|
||||
form.applyReading(form.empty, { weight: 7150, dsd: 1, mode: 'AUTO' })
|
||||
expect(form.missingWeighingFields('empty')).toEqual([])
|
||||
})
|
||||
|
||||
// ── Contrepartie conditionnelle (RG-5.03) ────────────────────────────────
|
||||
it('CLIENT : ne conserve que le client, purge supplier et otherLabel', () => {
|
||||
const form = useWeighingTicketForm()
|
||||
|
||||
@@ -65,6 +65,19 @@ function isoDateOnly(value: string | null | undefined): string | null {
|
||||
return value ? value.slice(0, 10) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Retire les clés à valeur `null` d'un payload (pattern « omission des requis
|
||||
* vides » M1). Avec `collectDenormalizationErrors` côté back, envoyer `null` sur
|
||||
* un scalaire requis (ex. `counterpartyType`) produit une violation de TYPE
|
||||
* opaque (« Cette valeur doit être de type string. ») au lieu du message métier
|
||||
* `NotBlank` : une clé ABSENTE laisse au contraire jouer la contrainte `NotBlank`
|
||||
* et son message FR. On omet donc les null ; les champs réellement requis non
|
||||
* remplis déclenchent leur vrai message, les optionnels restent simplement absents.
|
||||
*/
|
||||
function compact(payload: Record<string, unknown>): Record<string, unknown> {
|
||||
return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== null))
|
||||
}
|
||||
|
||||
/** Crée l'état initial d'un bloc de pesée (date = aujourd'hui, RG-5.07). */
|
||||
function emptyBlock(today: string): WeighingBlockState {
|
||||
return {
|
||||
@@ -122,6 +135,21 @@ export function useWeighingTicketForm() {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Champs de pesée manquants d'un bloc (Poids / DSD), RG-5.07. Le back rend ces
|
||||
* colonnes nullable (workflow 2 temps) : l'obligation « une pesée a été
|
||||
* effectuée » est donc portée côté front (règle front-only, ERP-101). Renvoie
|
||||
* les `propertyPath` manquants (ex. `['emptyWeight', 'emptyDsd']`), prêts à
|
||||
* être posés en erreur inline via `useFormErrors.setError`.
|
||||
*/
|
||||
function missingWeighingFields(which: 'empty' | 'full'): string[] {
|
||||
const block = which === 'empty' ? empty : full
|
||||
const missing: string[] = []
|
||||
if (block.weight === null) missing.push(`${which}Weight`)
|
||||
if (block.dsd === null) missing.push(`${which}Dsd`)
|
||||
return missing
|
||||
}
|
||||
|
||||
/** Applique une lecture de pesée (bascule/manuelle) à un bloc. */
|
||||
function applyReading(
|
||||
block: WeighingBlockState,
|
||||
@@ -150,7 +178,7 @@ export function useWeighingTicketForm() {
|
||||
* pour que `useFormErrors` mappe les 422 inline.
|
||||
*/
|
||||
function buildCreatePayload(): Record<string, unknown> {
|
||||
return {
|
||||
return compact({
|
||||
counterpartyType: counterpartyType.value,
|
||||
...counterpartyPayload(),
|
||||
immatriculation: immatriculation.value || null,
|
||||
@@ -160,7 +188,7 @@ export function useWeighingTicketForm() {
|
||||
emptyDsd: empty.dsd,
|
||||
emptyMode: empty.mode,
|
||||
emptyManualNumber: empty.manualNumber || null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,7 +236,7 @@ export function useWeighingTicketForm() {
|
||||
* recalculé serveur (RG-5.05).
|
||||
*/
|
||||
function buildFullPayload(): Record<string, unknown> {
|
||||
return {
|
||||
return compact({
|
||||
immatriculation: immatriculation.value || null,
|
||||
plateFreeFormat: plateFreeFormat.value,
|
||||
fullDate: full.date || null,
|
||||
@@ -216,7 +244,7 @@ export function useWeighingTicketForm() {
|
||||
fullDsd: full.dsd,
|
||||
fullMode: full.mode,
|
||||
fullManualNumber: full.manualNumber || null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -234,6 +262,7 @@ export function useWeighingTicketForm() {
|
||||
empty,
|
||||
full,
|
||||
applyReading,
|
||||
missingWeighingFields,
|
||||
// workflow
|
||||
ticketId,
|
||||
hydrate,
|
||||
|
||||
Reference in New Issue
Block a user