Entité WeighingTicket - Entité métier complète (#[Auditable], TimestampableBlamableTrait, relations ORM Client/Supplier/Site) + contrat de sérialisation à 3 maillons (weighing_ticket:read / :item:read + contextes par opération). - Getters calculés displayDate et plateFreeFormat (#[SerializedName]), sécurité view/manage, pas de Delete/archive. - Validation #[Assert\*] messages FR + #[Assert\Callback] RG-5.03 (->atPath()), libellé i18n audit.entity.logistique_weighingticket. - Repository : interface Domain + DoctrineWeighingTicketRepository (recherche + tri number DESC, deletedAt IS NULL). Dette site.code - Site.code mappé VARCHAR(8) (groupes read/write), dérivation auto au PrePersist (2 premiers chiffres du CP), UniqueConstraint uq_site_code. - Migration Version20260617160000 : ALTER COLUMN code SET NOT NULL + COMMENT. - Fixtures (codes 86/17/82) et SiteApiTest ajustés. Câblage - doctrine.yaml : mapping ORM du module Logistique (absent du scaffold ERP-181). - ColumnCommentsCatalog : site.code + table weighing_ticket. Specs M5 versionnées (spec-back / spec-front / prompts).
18 KiB
module, nom, ecran, owner_spec, backup_spec, version, date_redaction, maquette_figma, regles_metier, roles, lien_spec_back, client_validation_1, lesstime_project_id, lesstime_taskgroup_id, statut_global
| module | nom | ecran | owner_spec | backup_spec | version | date_redaction | maquette_figma | regles_metier | roles | lien_spec_back | client_validation_1 | lesstime_project_id | lesstime_taskgroup_id | statut_global | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| M5 | Tickets de pesée | tickets-pesee | Matthieu | Tristan | V0.1 | 2026-06-17 | https://www.figma.com/design/jRYgT0T9c03VsEbjGhCwwS/Composants---Design-System?node-id=1322-16774&p=f&m=dev |
|
|
./spec-back.md |
|
6 | 33 | pret_a_dev |
Module 5 — Tickets de pesée (V0.1 front)
Origine : spec fonctionnelle
M5-ticket-de-pesee-V02(V0.2, 15/06/2026, validation client en attente) + maquette Figma (node 1322-16774). Restitution Markdown pour intégration au workflow MALIO. Toute décision technique (back) vit dansspec-back.md. Le M5 réutilise le pattern et les composants posés aux M1 clients → M4 transporteurs.
Nouveau module
Logistique(DÉCISION Matthieu 17/06). La maquette montre une section sidebar Logistique plus large (Réception, Expédition, Validations, Triage, Ticket de pesée, Bons…) ; le M5 ne livre que l'écran « Ticket de pesée ». Les autres items sont hors périmètre (modules/écrans ultérieurs).
Décisions (17/06) : (1) pont bascule = stub renvoyant un poids aléatoire ∈ [10000, 50000] kg (pas de liaison matérielle —
spec-back.md § 2.6) ; (2) DSD = compteur de pesée par site, +1 par pesée (§ 2.7) ; (3) net = plein − vide (§ 2.8) ; (4) numéro{siteCode}-TP-{NNNN}par site (§ 2.5).
But
Lister les tickets de pesée et accéder à leur fiche : consultation, création (pesée à vide + pesée à plein au pont bascule), modification, impression. Chaque ticket porte un numéro unique par site (ex. 86-TP-0001) et une contrepartie Client / Fournisseur / Autre.
Accès
- Depuis : menu principal → section Logistique → item « Ticket de pesée » (route
/weighing-tickets). - Site : l'écran dépend du site courant (sélecteur de site en haut de l'app — onglets
CHÂTELLERAULT/SAINT-JEAN/POMMEVIC). Le site pilote la numérotation et (par défaut) le cloisonnement de la liste (spec-back.md § 2.3 / § 2.5). - Rôles autorisés (tableau « Rôles & permissions » du docx p.3, V0.2) :
| Rôle | Consultation | Ajout / Modification |
|---|---|---|
| Admin | ✅ Tout | ✅ Tout |
| Bureau | ✅ Tout | ✅ Tout |
| Usine | ✅ Tout | ✅ Tout |
| Compta | ❌ | ❌ |
| Commerciale | ❌ | ❌ |
Notes :
- RBAC transposée sur
logistique.weighing_tickets.view/.manage(spec-back.md § 5).- ⚠ Changement vs M5 V0.1 : en V0.2, Usine = Tout / Tout. Compta et Commerciale n'ont aucun accès (item sidebar masqué).
Navigation
Page d'entrée de l'écran : datatable « Tickets de pesées ».
- Clic sur une ligne → écran Modification d'un ticket de pesée (le docx ne prévoit pas d'écran de consultation séparé — clic = édition).
- Bouton « + Ajouter » (haut droite, si
manage) → écran Ajouter un ticket de pesée. - Bouton « Exporter » (bas de liste, maquette) → télécharge un XLSX de toute la liste (filtres + site courant appliqués). Format dans
spec-back.md § 4.5.
Datatable des tickets
Composant : <MalioDataTable> branché sur usePaginatedList<WeighingTicket>({ url: '/weighing_tickets' }) (URL API en snake_case ; la route Nuxt reste /weighing-tickets) (règle frontend obligatoire — pagination Hydra, état 100 % local). Colonnes (docx p.3 + maquette) :
| Colonne | Source | Tri |
|---|---|---|
| Numéro | ticket.number ({siteCode}-TP-{NNNN}) |
DESC par défaut (plus récents en tête) |
| Client | ticket.client.companyName (vide si contrepartie ≠ Client) |
Non |
| Fournisseur | ticket.supplier.companyName (vide si ≠ Fournisseur) |
Non |
| Autre | ticket.otherLabel (vide si ≠ Autre) |
Non |
| Date | ticket.displayDate (fullDate ?? emptyDate, format JJ-MM-AAAA) |
Oui |
| Poids | ticket.netWeight (kg, = plein − vide — RG-5.05) |
Oui |
Clic ligne → écran Modification. Pagination : standard Starseed 10 / 25 / 50 (défaut 10). Liste cloisonnée par site courant par défaut (
spec-back.md § 2.3).
Écran « Ajouter un ticket de pesée »
Accès : bouton « + Ajouter ». Rôles : Admin, Bureau, Usine. Titre : « ← Ticket de pesée » (flèche retour vers la liste).
L'écran (maquette) est composé de deux blocs empilés — « Poids à vide » puis « Poids à plein » — et d'un bouton « Valider » en bas.
Bloc « Poids à vide »
Boutons en haut à droite du bloc : « Pesée bascule » (<MalioButton> secondaire) + « Pesée manuelle » (<MalioButton> primaire).
Champs :
| Champ | Type composant | Obligatoire | Règle |
|---|---|---|---|
| Fournisseur / Client / Autre | <MalioSelect> (3 valeurs) |
Oui | RG-5.03 — pilote le champ suivant |
| Nom du fournisseur | <MalioSelect> (liste fournisseurs M2) |
Conditionnel | RG-5.03 — visible + obligatoire si « Fournisseur » |
| Nom du client | <MalioSelect> (liste clients M1) |
Conditionnel | RG-5.03 — visible + obligatoire si « Client » |
| Autre | <MalioInputText> |
Conditionnel | RG-5.03 — visible + obligatoire si « Autre » |
| Date | <MalioInputText> type date (cf. note) |
Oui | RG-5.07 — date du jour par défaut |
| Poids | <MalioInputNumber> (suffixe « Kg ») |
Oui | RG-5.07 — readonly, rempli par la pesée |
| DSD | <MalioInputNumber> |
Oui | RG-5.04 / RG-5.07 — readonly, rempli par la pesée |
| Immatriculation | <MalioInputText> (masque XX-000-XX) |
Oui | RG-5.01 |
| Tout format | <MalioCheckbox> |
Non | RG-5.01 — désactive le masque |
La contrepartie (Fournisseur/Client/Autre) + son champ associé est portée par le bloc « Poids à vide » uniquement (maquette) — c'est une donnée du ticket, pas répétée sur le bloc plein. Côté back : champs
counterpartyType/client/supplier/otherLabeldu ticket (spec-back.md § 2.9).
Action « Enregistrer » (sous le bloc, maquette) : POST /api/weighing_tickets (création initiale du ticket avec la pesée à vide) — spec-back.md § 4.3. Le numéro {siteCode}-TP-{NNNN} est attribué serveur.
Bloc « Poids à plein »
Mêmes boutons « Pesée bascule » + « Pesée manuelle ». Champs : Date (date du jour par défaut), Poids (readonly, Kg), DSD (readonly), Immatriculation (XX-000-XX), « Tout format ».
Immatriculation + « Tout format » connectés entre les 2 blocs (RG-5.01) : une seule valeur partagée — modifier l'un met à jour l'autre (même véhicule). Géré dans
useWeighingTicketForm()(état partagé).
Boutons de pesée — comportement
| Bouton | Déclencheur | Comportement |
|---|---|---|
| Pesée bascule | clic | Ouvre une modal de confirmation « Êtes-vous sûr de vouloir déclencher une pesée ? » (<MalioButton> « Valider »). Si confirmé → POST /api/weighbridge_readings { mode: 'AUTO' } (spec-back.md § 4.2) → remplit Poids et DSD du bloc, ferme la modal. En cas d'erreur (RG-5.06) : le message d'erreur s'affiche dans la modal et invite à passer en pesée manuelle. (Au M5, le stub renvoie toujours un poids ∈ [10000,50000] — le chemin d'erreur est néanmoins géré.) |
| Pesée manuelle | clic | Ouvre une modal « Pesée manuelle » avec Poids et Numéro de pesée à saisir (<MalioInputNumber> + <MalioInputText>), bouton « Enregistrer ». Une fois validé → le Poids du bloc est rempli ; le DSD est calculé automatiquement = dernier dsd du site + 1 (POST /api/weighbridge_readings { mode: 'MANUAL', weight, manualNumber } — RG-5.04). |
Action « Valider » (bas d'écran)
<MalioButton> « Valider » → finalise le ticket (PATCH /api/weighing_tickets/{id} avec la pesée à plein + recalcul du net — spec-back.md § 4.4) puis ouvre la modal d'impression du ticket (RG-5.08 — bon d'impression réalisé par Tristan, cf. § Modales).
Écran « Modification d'un ticket de pesée »
But : modifier un ticket existant et/ou imprimer le ticket. Accès : clic sur une ligne de la liste. Rôles : Admin, Bureau, Usine.
Identique à l'écran d'ajout — mêmes 2 blocs, mêmes règles (RG-5.01 → RG-5.10) — sauf (docx + maquette) :
- Les champs sont pré-remplis avec les valeurs actuelles.
- Le bouton « Enregistrer » du bloc « Poids à vide » disparaît (RG-5.08) — on enregistre via le bas d'écran.
- En bas : « Enregistrer » (remplace « Valider ») + « Imprimer » (bouton d'impression absent à l'ajout, RG-5.08).
- Le numéro et le site sont immuables (lecture seule).
Modales
| Modale | Contenu | Source |
|---|---|---|
| Confirmation pesée bascule | « Êtes-vous sûr de vouloir déclencher une pesée ? » + bouton « Valider ». Erreur affichée inline → invite pesée manuelle (RG-5.06). | docx p.5 + maquette |
| Pesée manuelle | Champs « Poids » + « Numéro de pesée » + bouton « Enregistrer ». DSD auto = dernier +1 (RG-5.04). | docx p.5 + maquette |
| Impression du ticket / bon de pesée | Aperçu imprimable du ticket (numéro, contrepartie, immat, pesée vide/plein, net, DSD, date). Réalisé par Tristan (voir encadré ci-dessous). | docx p.5 / RG-5.08 ; spec-back.md § 2.12 |
⚠ Bon d'impression = Tristan. La conception et la réalisation du bon d'impression (gabarit du ticket de pesée, mise en page, déclenchement) sont prises en charge par Tristan lui-même, hors de la découpe front standard du M5. Le reste de l'écran (modale de confirmation, modale pesée manuelle, formulaires) reste dans la découpe M5.
- Déclencheur attendu : modale d'impression à la validation (création) ; bouton « Imprimer » en modification (absent à l'ajout — RG-5.08).
- Données disponibles : toute la réponse
GET /api/weighing_tickets/{id}(numéro, site, contrepartie, immat, pesées vide/plein, net, DSD, dates) —spec-back.md § 2.12 / § 4.0.- Modales : réutiliser le wrapper de modal partagé
frontend/shared/(comme M1→M4).
Composants UI à utiliser (@malio/layer-ui)
- Datatable :
<MalioDataTable>(+usePaginatedList) - Select :
<MalioSelect>(contrepartie, nom client, nom fournisseur) - Input texte :
<MalioInputText>(Autre, Immatriculation, Numéro de pesée) - Input nombre :
<MalioInputNumber>(Poids, DSD) - Checkbox :
<MalioCheckbox>(« Tout format ») - Bouton :
<MalioButton>,<MalioButtonIcon>(Pesée bascule, Pesée manuelle, Valider, Enregistrer, Imprimer, + Ajouter, Exporter) - Validation par champ :
useFormErrors(mapping 422 inline — règle frontend obligatoire) - Toasts : standards via
useApi()
Exceptions autorisées (commenter // TODO migrer quand Malio couvre) :
- Date :
<MalioInput>ne couvrant pasdatenativement, utiliser un<input type="date">encapsulé OUMalioDatesi dispo (cf. exceptions @.claude/rules/frontend.md — typedateexplicitement listé comme exception tolérée). - Masque immatriculation
XX-000-XX: si non couvert par<MalioInputText>, masque local (directive) +// TODO. La validation de format reste autoritaire côté serveur (RG-5.01 / RG-5.10). - Modales : wrapper partagé
frontend/shared/.
Composables & appels API
usePaginatedList<WeighingTicket>({ url: '/weighing_tickets' })— liste paginée (obligatoire). Consommenumber,client/supplier/otherLabel,displayDate,netWeight(spec-back.md § 4.0).useWeighingTicket(id)— charge le détail viaGET /api/weighing_tickets/{id}(pesées vide + plein embarquées, client/supplier/site imbriqués). DoD avant intégration : vérifier le JSON réel (spec-back.md § 4.0.bis).useWeighingTicketForm()— workflow 2 blocs (POST à l'« Enregistrer » du bloc vide, PATCH au « Valider ») + état partagé immatriculation/« Tout format » entre les 2 blocs (RG-5.01) + gestion des champs conditionnels de contrepartie (RG-5.03).useWeighbridge()— déclenche la pesée :POST /api/weighbridge_readings(AUTO ou MANUAL), gère la modal de confirmation et le chemin d'erreur → pesée manuelle (RG-5.06).useClientOptions()/useSupplierOptions()— alimentent les selects (référentiels M1/M2 via?pagination=false— échappatoire selects).useCurrentSite()— site courant (sélecteur) — déjà exposé côté front (Sites). Le back lit le site courant pour la numérotation ; le front n'a pas à l'envoyer.usePermissions()— masque l'item sidebar et les boutons selonlogistique.weighing_tickets.view/manage.- Tous les appels passent par
useApi()(jamais$fetchdirect — règle ABSOLUE n°4).
Règles de formatage et normalisation
Le serveur normalise systématiquement (spec-back.md § 6) :
| Champ | Normalisation serveur | Affichage front |
|---|---|---|
| Immatriculation | trim + UPPER ; format XX-000-XX sauf « Tout format » (RG-5.01) |
UPPER, masqué |
Autre (otherLabel) |
trim | identique |
| Poids / DSD | entiers (kg) | « 7 150 Kg », DSD brut |
| Numéro de ticket | {siteCode}-TP-{NNNN} (serveur) |
affiché tel quel |
Le front ne normalise pas : il envoie la valeur saisie, le serveur normalise et renvoie la valeur que l'UI affiche.
Différences notables avec les modules précédents
| Zone | M1→M4 | M5 tickets de pesée |
|---|---|---|
| Module | Commercial / Transport… | Logistique (nouveau, ERP à venir) |
| Saisie poids | — | Pesée au pont bascule (stub random) + pesée manuelle |
| Cloisonnement par site | M3 oui / M4 non | Oui (site courant) + numéro par site |
| Numérotation métier | id technique | {siteCode}-TP-{NNNN} par site (RG-5.02) |
| Onglets | présents | Aucun onglet : 2 blocs empilés (vide + plein) |
| Impression | aucune | Modal d'impression du ticket (RG-5.08) |
| Contrepartie | — | Client / Fournisseur / Autre (conditionnel, RG-5.03) |
Points résolus côté back
| # | Zone d'ombre | Résolution (cf. spec-back.md) |
|---|---|---|
| 1 | Module | Nouveau module Logistique (§ 2.1) |
| 2 | Pont bascule | Stub poids aléatoire ∈ [10000,50000], interface réutilisable, driver réel HP (§ 2.6) |
| 3 | DSD | Compteur de pesée par site, +1 par pesée ; manuel = dernier +1 (§ 2.7) |
| 4 | Poids net | plein − vide, calculé serveur (§ 2.8) |
| 5 | Numérotation | {siteCode}-TP-{NNNN} par site, séquence verrouillée (§ 2.5) ; ajout site.code |
| 6 | Contrepartie | counterpartyType + FK Client/Supplier ou otherLabel (RG-5.03, § 2.9) |
| 7 | Deux pesées | Colonnes plates empty_* / full_* ; les 2 blocs supportent bascule + manuelle (§ 2.4) |
| 8 | Impression | Modal d'impression front ; bouton dispo en modif seulement (RG-5.08, § 2.12) |
| 9 | Masque immat | XX-000-XX + « Tout format », connectés entre blocs (RG-5.01, § 2.10) |
| 10 | RBAC | logistique.weighing_tickets.view/manage ; Usine = Tout ; Compta + Commerciale sans accès (§ 5.2) |
📦 Tickets Lesstime générés
TaskGroup Lesstime : #33 — M5 — Tickets de pesée (projet ERP / Starseed, projectId=6) — créé le 17/06/2026, 12 tickets au statut « Prêt à dev ».
| # | ERP | Ticket | Effort | Tag |
|---|---|---|---|---|
| 1.1 | ERP-181 | Scaffolder le module Logistique + RBAC | M | Backend |
| 1.2 | ERP-182 | Migrer le schéma M5 (site.code, compteurs, weighing_ticket) | M | Backend |
| 1.3 | ERP-183 | Créer l'entité WeighingTicket + repository + contrat sérialisation | M | Backend |
| 1.4 | ERP-184 | Implémenter la pesée pont bascule (stub + DSD + endpoint) | M | Backend |
| 1.5 | ERP-185 | Créer Provider + Processor (numérotation, RG, normalisation) | L | Backend |
| 1.6 | ERP-186 | Implémenter l'export XLSX | S | Backend |
| 1.7 | ERP-187 | Tests PHPUnit RG-5.01→5.10 + capture contrat JSON | M | Backend |
| 1.8 | ERP-188 | Créer la page liste /weighing-tickets + export |
M | Frontend |
| 1.9 | ERP-189 | Implémenter l'écran Ajouter (blocs vide+plein, pesée, masque immat) | L | Frontend |
| 1.10 | ERP-190 | Implémenter l'écran Modification + déclenchement impression | M | Frontend |
| 1.11 | ERP-191 | i18n + libellés + branchement site courant | S | Frontend |
| 1.12 | ERP-192 | Bon d'impression du ticket de pesée — OWNER Tristan | — | Frontend |