391f383c4b
Un brouillon dont le type de contrepartie est choisi sans son champ associé (client/fournisseur null, ou libellé « Autre » vide) violait chk_wt_*_branch et levait une 500 : le callback de cohérence RG-5.03 ne joue qu'au groupe finalize, laissant passer l'incohérence à l'enregistrement du brouillon. - back : WeighingTicketProcessor retire la contrepartie entière quand le champ de branche est absent (clearCounterparty) au lieu de persister un état incohérent. N'affecte que le brouillon (à la validation, le callback finalize lève déjà une 422 avant le processor). - front : buildDraftPayload n'émet le type que si son champ associé est rempli ; la validation continue d'envoyer toujours le type pour la 422 métier. - tests : 2 cas back (CLIENT sans client, AUTRE libellé vide) + 2 cas front.
229 lines
11 KiB
TypeScript
229 lines
11 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
|
|
// `nowIsoDateTime` est importé par le composable : on le stubbe pour un instant déterministe.
|
|
vi.mock('~/shared/utils/date', () => ({ nowIsoDateTime: () => '2026-06-22T08:30:00' }))
|
|
|
|
const { useWeighingTicketForm } = await import('../useWeighingTicketForm')
|
|
|
|
describe('useWeighingTicketForm', () => {
|
|
it('initialise les 2 blocs à la date/heure courante (RG-5.07), sans poids ni DSD', () => {
|
|
const form = useWeighingTicketForm()
|
|
expect(form.empty.date).toBe('2026-06-22T08:30:00')
|
|
expect(form.full.date).toBe('2026-06-22T08:30:00')
|
|
expect(form.empty.weight).toBeNull()
|
|
expect(form.empty.dsd).toBeNull()
|
|
expect(form.counterpartyType.value).toBeNull()
|
|
})
|
|
|
|
// ── Omission des requis vides (compact) ──────────────────────────────────
|
|
it('buildDraftPayload : brouillon vierge → pas de champ requis ni de bloc non pesé', () => {
|
|
const form = useWeighingTicketForm()
|
|
// Formulaire vierge : counterpartyType / immatriculation non remplis, aucune pesée.
|
|
const payload = form.buildDraftPayload()
|
|
// Absents (et non null) → le back laisse jouer les contraintes du groupe finalize.
|
|
expect(payload).not.toHaveProperty('counterpartyType')
|
|
expect(payload).not.toHaveProperty('immatriculation')
|
|
// Bloc non pesé → ni poids ni date (on n'envoie pas une date de pesée sans pesée).
|
|
expect(payload).not.toHaveProperty('emptyWeight')
|
|
expect(payload).not.toHaveProperty('emptyDate')
|
|
// Seul le booléen « Tout format » reste.
|
|
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()
|
|
form.supplierIri.value = '/api/suppliers/3'
|
|
form.otherLabel.value = 'Particulier'
|
|
|
|
form.setCounterpartyType('CLIENT')
|
|
form.clientIri.value = '/api/clients/629'
|
|
|
|
expect(form.counterpartyField.value).toBe('client')
|
|
expect(form.supplierIri.value).toBeNull()
|
|
expect(form.otherLabel.value).toBeNull()
|
|
|
|
const payload = form.buildDraftPayload()
|
|
expect(payload.counterpartyType).toBe('CLIENT')
|
|
expect(payload.client).toBe('/api/clients/629')
|
|
expect(payload).not.toHaveProperty('supplier')
|
|
expect(payload).not.toHaveProperty('otherLabel')
|
|
})
|
|
|
|
it('FOURNISSEUR : ne conserve que le supplier', () => {
|
|
const form = useWeighingTicketForm()
|
|
form.clientIri.value = '/api/clients/1'
|
|
form.setCounterpartyType('FOURNISSEUR')
|
|
form.supplierIri.value = '/api/suppliers/7'
|
|
|
|
expect(form.counterpartyField.value).toBe('supplier')
|
|
expect(form.clientIri.value).toBeNull()
|
|
expect(form.buildDraftPayload().supplier).toBe('/api/suppliers/7')
|
|
})
|
|
|
|
it('AUTRE : ne conserve que le libellé libre', () => {
|
|
const form = useWeighingTicketForm()
|
|
form.clientIri.value = '/api/clients/1'
|
|
form.setCounterpartyType('AUTRE')
|
|
form.otherLabel.value = 'Reprise interne'
|
|
|
|
expect(form.counterpartyField.value).toBe('other')
|
|
expect(form.clientIri.value).toBeNull()
|
|
expect(form.buildDraftPayload().otherLabel).toBe('Reprise interne')
|
|
})
|
|
|
|
it('buildDraftPayload : type choisi mais champ associé vide → contrepartie omise (pas de 500 chk_wt_*_branch)', () => {
|
|
const form = useWeighingTicketForm()
|
|
// L'opérateur ouvre le menu « Client » mais n'a pas encore choisi le client.
|
|
form.setCounterpartyType('CLIENT')
|
|
|
|
const draft = form.buildDraftPayload()
|
|
// On n'émet ni le type ni la FK : un brouillon incohérent serait rejeté en 500 par le back.
|
|
expect(draft).not.toHaveProperty('counterpartyType')
|
|
expect(draft).not.toHaveProperty('client')
|
|
|
|
// En revanche la validation envoie toujours le type, pour déclencher la 422 métier.
|
|
expect(form.buildValidatePayload().counterpartyType).toBe('CLIENT')
|
|
})
|
|
|
|
it('buildDraftPayload : AUTRE avec libellé vide → contrepartie omise', () => {
|
|
const form = useWeighingTicketForm()
|
|
form.setCounterpartyType('AUTRE')
|
|
form.otherLabel.value = ' '
|
|
|
|
const draft = form.buildDraftPayload()
|
|
expect(draft).not.toHaveProperty('counterpartyType')
|
|
expect(draft).not.toHaveProperty('otherLabel')
|
|
})
|
|
|
|
// ── Immatriculation / « Tout format » partagés entre blocs (RG-5.01) ──────
|
|
it('immatriculation et plateFreeFormat sont partagés (une seule valeur)', () => {
|
|
const form = useWeighingTicketForm()
|
|
form.immatriculation.value = 'AB-123-CD'
|
|
form.plateFreeFormat.value = true
|
|
|
|
// Les 2 payloads (brouillon + validation) reflètent la même valeur.
|
|
expect(form.buildDraftPayload().immatriculation).toBe('AB-123-CD')
|
|
expect(form.buildDraftPayload().plateFreeFormat).toBe(true)
|
|
expect(form.buildValidatePayload().immatriculation).toBe('AB-123-CD')
|
|
expect(form.buildValidatePayload().plateFreeFormat).toBe(true)
|
|
})
|
|
|
|
// ── Application d'une lecture de pesée ────────────────────────────────────
|
|
it('applyReading remplit poids / DSD / mode et ré-horodate le bloc à l\'instant de la pesée', () => {
|
|
const form = useWeighingTicketForm()
|
|
// Date périmée (ouverture du formulaire bien avant la pesée).
|
|
form.empty.date = '2020-01-01T00:00:00'
|
|
form.applyReading(form.empty, { weight: 7150, dsd: 1, mode: 'AUTO' })
|
|
// La pesée validée ré-horodate le bloc à maintenant (stub 2026-06-22T08:30:00).
|
|
expect(form.empty.date).toBe('2026-06-22T08:30:00')
|
|
expect(form.empty.weight).toBe(7150)
|
|
expect(form.empty.dsd).toBe(1)
|
|
expect(form.empty.mode).toBe('AUTO')
|
|
|
|
// Pesée manuelle : le DSD saisi (16619) est conservé tel quel (ERP-193).
|
|
form.applyReading(form.full, { weight: 14300, dsd: 16619, mode: 'MANUAL' })
|
|
expect(form.full.weight).toBe(14300)
|
|
expect(form.full.dsd).toBe(16619)
|
|
expect(form.full.mode).toBe('MANUAL')
|
|
})
|
|
|
|
it('buildDraftPayload porte les pesées effectuées ; buildValidatePayload les 4 champs du haut', () => {
|
|
const form = useWeighingTicketForm()
|
|
form.setCounterpartyType('CLIENT')
|
|
form.clientIri.value = '/api/clients/1'
|
|
form.immatriculation.value = 'AB-123-CD'
|
|
form.applyReading(form.empty, { weight: 7150, dsd: 1, mode: 'AUTO' })
|
|
form.applyReading(form.full, { weight: 14300, dsd: 2, mode: 'AUTO' })
|
|
|
|
// Le brouillon porte LES DEUX pesées effectuées.
|
|
const draft = form.buildDraftPayload()
|
|
expect(draft.emptyWeight).toBe(7150)
|
|
expect(draft.emptyMode).toBe('AUTO')
|
|
expect(draft.fullWeight).toBe(14300)
|
|
expect(draft.fullMode).toBe('AUTO')
|
|
|
|
// La validation ne porte que les 4 champs du haut (pesées déjà persistées).
|
|
const validate = form.buildValidatePayload()
|
|
expect(validate.counterpartyType).toBe('CLIENT')
|
|
expect(validate.client).toBe('/api/clients/1')
|
|
expect(validate.immatriculation).toBe('AB-123-CD')
|
|
expect(validate).not.toHaveProperty('emptyWeight')
|
|
expect(validate).not.toHaveProperty('fullWeight')
|
|
})
|
|
|
|
// ── Pré-remplissage (écran Modification, ERP-190) ─────────────────────────
|
|
it('hydrate pré-remplit l\'état depuis le détail (datetime ISO ramené en local, heure conservée)', () => {
|
|
const form = useWeighingTicketForm()
|
|
form.hydrate({
|
|
id: 9,
|
|
counterpartyType: 'CLIENT',
|
|
client: { '@id': '/api/clients/629' },
|
|
immatriculation: 'AB-123-CD',
|
|
plateFreeFormat: false,
|
|
emptyDate: '2026-06-17T09:00:00+02:00',
|
|
emptyWeight: 7150,
|
|
emptyDsd: 1,
|
|
emptyMode: 'AUTO',
|
|
fullDate: '2026-06-17T09:12:00+02:00',
|
|
fullWeight: 14300,
|
|
fullDsd: 2,
|
|
fullMode: 'AUTO',
|
|
})
|
|
|
|
expect(form.ticketId.value).toBe(9)
|
|
expect(form.counterpartyType.value).toBe('CLIENT')
|
|
expect(form.counterpartyField.value).toBe('client')
|
|
expect(form.clientIri.value).toBe('/api/clients/629')
|
|
expect(form.immatriculation.value).toBe('AB-123-CD')
|
|
// Datetime back (avec fuseau) -> local sans fuseau, heure conservée pour MalioDateTime.
|
|
expect(form.empty.date).toBe('2026-06-17T09:00:00')
|
|
expect(form.full.date).toBe('2026-06-17T09:12:00')
|
|
expect(form.empty.weight).toBe(7150)
|
|
expect(form.full.weight).toBe(14300)
|
|
})
|
|
|
|
it('hydrate gère les champs null omis (skip_null_values) avec des défauts', () => {
|
|
const form = useWeighingTicketForm()
|
|
form.hydrate({ id: 5, counterpartyType: 'AUTRE', otherLabel: 'Reprise' })
|
|
expect(form.otherLabel.value).toBe('Reprise')
|
|
expect(form.supplierIri.value).toBeNull()
|
|
expect(form.plateFreeFormat.value).toBe(false)
|
|
// Pas de date back -> repli sur l'instant courant (stub 2026-06-22T08:30:00).
|
|
expect(form.empty.date).toBe('2026-06-22T08:30:00')
|
|
expect(form.empty.weight).toBeNull()
|
|
})
|
|
|
|
it('buildDraftPayload après hydrate porte contrepartie + véhicule + les 2 pesées', () => {
|
|
const form = useWeighingTicketForm()
|
|
form.hydrate({
|
|
id: 9,
|
|
status: 'VALIDATED',
|
|
counterpartyType: 'CLIENT',
|
|
client: { '@id': '/api/clients/629' },
|
|
immatriculation: 'AB-123-CD',
|
|
emptyWeight: 7150, emptyDsd: 1, emptyMode: 'AUTO',
|
|
fullWeight: 14300, fullDsd: 2, fullMode: 'AUTO',
|
|
})
|
|
|
|
expect(form.status.value).toBe('VALIDATED')
|
|
|
|
const payload = form.buildDraftPayload()
|
|
expect(payload.counterpartyType).toBe('CLIENT')
|
|
expect(payload.client).toBe('/api/clients/629')
|
|
expect(payload.emptyWeight).toBe(7150)
|
|
expect(payload.fullWeight).toBe(14300)
|
|
expect(payload.immatriculation).toBe('AB-123-CD')
|
|
})
|
|
})
|