feat : M5 — Tickets de pesée (ERP-188 → ERP-193) (#144)
Auto Tag Develop / tag (push) Successful in 8s
Auto Tag Develop / tag (push) Successful in 8s
MR unique regroupant tout le module M5 « Tickets de pesée » (remplace les MR empilées #140/#141/#142/#143).
## Périmètre
- **ERP-188** — Page liste des tickets de pesée + export XLSX (colonnes Fournisseur/Client/Autre + Statut).
- **ERP-189** — Écran « Ajouter » (4 champs en haut, 2 blocs de pesée, pesée bascule/manuelle, date+heure horodatée à la validation).
- **ERP-190** — Écran « Modifier » + bouton Imprimer.
- **ERP-191** — i18n + libellés + branchement site courant.
- **ERP-192** — Bon de pesée PDF généré côté back (template Twig → Dompdf), endpoint `GET /api/weighing_tickets/{id}/print.pdf`.
- **ERP-193** — Cycle de vie brouillon/validé (status DRAFT/VALIDATED, numéro attribué à la validation), DSD saisi conservé en pesée manuelle, retours métier design.
## Vérifications
- Back : tests Logistique + architecture verts, php-cs-fixer propre, migrations appliquées (dev + test).
- Front : suite Vitest complète verte, ESLint propre.
Base : `develop` — contient les 16 commits du M5 (rien d'autre).
Reviewed-on: #144
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #144.
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { formatDateFr, formatWeightKg, formatPlate } from '../weighingTicketFormat'
|
||||
|
||||
describe('weighingTicketFormat', () => {
|
||||
// ── Date JJ-MM-AAAA ───────────────────────────────────────────────────────
|
||||
describe('formatDateFr', () => {
|
||||
it('formate un datetime ISO en JJ-MM-AAAA', () => {
|
||||
expect(formatDateFr('2026-06-17T09:12:00+02:00')).toBe('17-06-2026')
|
||||
})
|
||||
|
||||
it('zéro-pad le jour et le mois', () => {
|
||||
expect(formatDateFr('2026-01-05T00:00:00Z')).toBe('05-01-2026')
|
||||
})
|
||||
|
||||
it('retourne une chaîne vide si absente ou invalide', () => {
|
||||
expect(formatDateFr(null)).toBe('')
|
||||
expect(formatDateFr(undefined)).toBe('')
|
||||
expect(formatDateFr('pas-une-date')).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
// ── Poids « X XXX Kg » ────────────────────────────────────────────────────
|
||||
describe('formatWeightKg', () => {
|
||||
it('ajoute un séparateur de milliers (espace) et le suffixe Kg', () => {
|
||||
expect(formatWeightKg(7150)).toBe('7 150 Kg')
|
||||
expect(formatWeightKg(14300)).toBe('14 300 Kg')
|
||||
expect(formatWeightKg(1000000)).toBe('1 000 000 Kg')
|
||||
})
|
||||
|
||||
it('gère les petits nombres sans séparateur', () => {
|
||||
expect(formatWeightKg(0)).toBe('0 Kg')
|
||||
expect(formatWeightKg(999)).toBe('999 Kg')
|
||||
})
|
||||
|
||||
it('retourne une chaîne vide si le poids est absent', () => {
|
||||
expect(formatWeightKg(null)).toBe('')
|
||||
expect(formatWeightKg(undefined)).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
// ── Immatriculation UPPER ─────────────────────────────────────────────────
|
||||
describe('formatPlate', () => {
|
||||
it('met en majuscules et trim', () => {
|
||||
expect(formatPlate(' ab-123-cd ')).toBe('AB-123-CD')
|
||||
})
|
||||
|
||||
it('retourne une chaîne vide si absente', () => {
|
||||
expect(formatPlate(null)).toBe('')
|
||||
expect(formatPlate('')).toBe('')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { MaskInputOptions } from 'maska'
|
||||
|
||||
/**
|
||||
* Masques de saisie du module « Tickets de pesée » (M5). Partagés entre le
|
||||
* composant de bloc (`WeighingBlock`) et les modales de pesée (écrans Ajouter /
|
||||
* Modifier). La validation de format reste autoritaire côté serveur (RG-5.01).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Masque « chiffres uniquement » (longueur libre) — Poids et DSD. Verrouille la
|
||||
* saisie sur des entiers.
|
||||
*/
|
||||
export const NUMERIC_MASK: MaskInputOptions = {
|
||||
mask: 'D',
|
||||
tokens: { D: { pattern: /[0-9]/, multiple: true } },
|
||||
}
|
||||
|
||||
/**
|
||||
* Masque plaque FR SIV `XX-000-XX` : 2 lettres, 3 chiffres, 2 lettres, majuscules
|
||||
* forcées. Utilisé quand « Tout format » n'est pas coché (RG-5.01).
|
||||
*/
|
||||
export const PLATE_MASK: MaskInputOptions = {
|
||||
mask: 'AA-###-AA',
|
||||
tokens: { A: { pattern: /[A-Za-z]/, transform: (c: string) => c.toUpperCase() } },
|
||||
}
|
||||
|
||||
/**
|
||||
* Masque « Tout format » (RG-5.01) : plaques anciennes / étrangères / engins. On
|
||||
* autorise lettres, chiffres, espace et tiret, en MAJUSCULES, longueur libre —
|
||||
* mais on filtre tout le reste (accents, ponctuation, symboles : « &é"'(_ç… »).
|
||||
* Pattern maska charset du projet (cf. shared/utils/textSanitize) : `preProcess`
|
||||
* retire d'abord les caractères hors charset (le token `multiple` glouton
|
||||
* s'arrêterait sinon au 1er invalide), puis le token laisse passer le reste.
|
||||
*/
|
||||
export const FREE_PLATE_MASK: MaskInputOptions = {
|
||||
mask: 'P',
|
||||
tokens: { P: { pattern: /[A-Z0-9 -]/, multiple: true } },
|
||||
preProcess: (value: string) => value.toUpperCase().replace(/[^A-Z0-9 -]/g, ''),
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Filtres d'affichage du module « Tickets de pesée » (M5, ERP-191). Helpers PURS
|
||||
* et testables, partagés par la liste et les écrans. Le serveur reste l'autorité
|
||||
* de normalisation (spec-front § Règles de formatage) : ces helpers ne font que
|
||||
* mettre en forme la valeur déjà normalisée renvoyée par l'API.
|
||||
*/
|
||||
|
||||
// Date courte française `JJ-MM-AAAA` (spec M5) : helper partagé inter-modules
|
||||
// (mutualisé avec les répertoires M1→M4). Re-exporté ici pour les écrans M5.
|
||||
export { formatDateFr } from '~/shared/utils/date'
|
||||
|
||||
/**
|
||||
* Poids en kg avec séparateur de milliers (espace) + suffixe « Kg »
|
||||
* (spec-front : « 7 150 Kg »). Chaîne vide si le poids est absent (ticket dont la
|
||||
* pesée à plein n'est pas finalisée). Groupement manuel (espace ASCII) pour un
|
||||
* rendu déterministe, indépendant de l'ICU de l'environnement.
|
||||
*/
|
||||
export function formatWeightKg(value: number | null | undefined): string {
|
||||
if (value === null || value === undefined) {
|
||||
return ''
|
||||
}
|
||||
const grouped = String(Math.round(value)).replace(/\B(?=(\d{3})+(?!\d))/g, ' ')
|
||||
return `${grouped} Kg`
|
||||
}
|
||||
|
||||
/**
|
||||
* Immatriculation en MAJUSCULES (cohérent avec la normalisation serveur RG-5.01 :
|
||||
* trim + UPPER). Chaîne vide si absente.
|
||||
*/
|
||||
export function formatPlate(value: string | null | undefined): string {
|
||||
return value ? value.trim().toUpperCase() : ''
|
||||
}
|
||||
Reference in New Issue
Block a user