Files
Starseed/docs/specs/M5-tickets-pesee/spec-front.md
T
Matthieu 4369c71706 feat(logistique) : entité WeighingTicket + dette site.code (ERP-183)
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).
2026-06-17 17:46:20 +02:00

18 KiB
Raw Blame History

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
RG-5.01
RG-5.02
RG-5.03
RG-5.04
RG-5.05
RG-5.06
RG-5.07
RG-5.08
RG-5.09
RG-5.10
Admin
Bureau
Compta
Commerciale
Usine
./spec-back.md
statut version date_doc date_validation valide_par
validee V0.2 2026-06-15 2026-06-17 Matthieu (CP MALIO)
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 dans spec-back.md. Le M5 réutilise le pattern et les composants posés aux M1 clientsM4 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 / otherLabel du 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 pas date nativement, utiliser un <input type="date"> encapsulé OU MalioDate si dispo (cf. exceptions @.claude/rules/frontend.md — type date explicitement 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). Consomme number, client/supplier/otherLabel, displayDate, netWeight (spec-back.md § 4.0).
  • useWeighingTicket(id) — charge le détail via GET /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 selon logistique.weighing_tickets.view/manage.
  • Tous les appels passent par useApi() (jamais $fetch direct — 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