feat(logistique) : WeighingTicketProvider + Processor — numérotation, contrepartie, net, normalisation (ERP-185) #135

Merged
matthieu merged 1 commits from feat/erp-185-provider-processor-weighingticket into develop 2026-06-18 12:47:12 +00:00
Owner

M5 · ERP-185 (1.5) — Provider + Processor WeighingTicket

Logique métier d'écriture (Processor) et de lecture (Provider) du ticket de pesée. Stack M5, basé sur ERP-184.

Processor WeighingTicketProcessor (POST/PATCH)

  • Site courant résolu via CurrentSiteProviderInterface (400 si aucun site sélectionné).
  • Numéro {siteCode}-TP-{NNNN} (RG-5.02) attribué à la création via WeighingTicketNumberAllocator (compteur weighing_ticket_counter, SELECT … FOR UPDATE, %04d). Numéro + site immuables au PATCH (RG-5.09).
  • Contrepartie (RG-5.03) : exclusivité — null-ification des champs hors-branche selon counterpartyType (la présence du champ requis reste validée par l'Assert\Callback de l'entité ; ici on évite la 500 des CHECK Postgres).
  • Immatriculation (RG-5.01/5.10) : trim + UPPER + masque XX-000-XX hors « Tout format » ; 422 inline sur immatriculation si invalide (consommable useFormErrors).
  • DSD autoritaire (RG-5.04) : (ré)attribution via DsdAllocator (verrou) pour les pesées AUTO ; les pesées MANUEL conservent leur DSD (déjà alloué par l'endpoint de pesée).
  • Poids net = plein − vide recalculé (RG-5.05).

Provider WeighingTicketProvider (GET)

  • Liste paginée (Paginator ORM — règle ABSOLUE n°13, jamais d'array brut).
  • Cloisonnement par site courant appliqué dans le provider : un provider custom court-circuite le SiteScopedQueryExtension, on réplique donc sa logique (bypass_scope / site null = no-op).
  • ?search= (number, nom client/fournisseur, other_label, immatriculation) ; tri ?order[displayDate] (COALESCE full/empty), défaut number DESC.
  • Anti-N+1 : fetch-join client/supplier/site (ManyToOne).
  • Item : 404 hors périmètre site ou soft-delete.

Vérifications

  • make test vert (811 tests), suite Architecture verte (CollectionsArePaginatedTest OK).
  • make php-cs-fixer-allow-risky propre.
  • Smoke logique normalizer (masque AB-123-CD, rejet 422, free format) + format numéro 86-TP-0002 validés.

Tests fonctionnels dédiés (numérotation/concurrence, contrat JSON, RG complètes) : ERP-187.

## M5 · ERP-185 (1.5) — Provider + Processor WeighingTicket Logique métier d'écriture (Processor) et de lecture (Provider) du ticket de pesée. Stack M5, basé sur ERP-184. ### Processor `WeighingTicketProcessor` (POST/PATCH) - **Site courant** résolu via `CurrentSiteProviderInterface` (400 si aucun site sélectionné). - **Numéro `{siteCode}-TP-{NNNN}`** (RG-5.02) attribué à la création via `WeighingTicketNumberAllocator` (compteur `weighing_ticket_counter`, `SELECT … FOR UPDATE`, `%04d`). Numéro + site **immuables** au PATCH (RG-5.09). - **Contrepartie** (RG-5.03) : exclusivité — null-ification des champs hors-branche selon `counterpartyType` (la présence du champ requis reste validée par l'`Assert\Callback` de l'entité ; ici on évite la 500 des CHECK Postgres). - **Immatriculation** (RG-5.01/5.10) : trim + UPPER + masque `XX-000-XX` hors « Tout format » ; **422 inline** sur `immatriculation` si invalide (consommable `useFormErrors`). - **DSD autoritaire** (RG-5.04) : (ré)attribution via `DsdAllocator` (verrou) pour les pesées **AUTO** ; les pesées MANUEL conservent leur DSD (déjà alloué par l'endpoint de pesée). - **Poids net** = plein − vide recalculé (RG-5.05). ### Provider `WeighingTicketProvider` (GET) - Liste **paginée** (`Paginator` ORM — règle ABSOLUE n°13, jamais d'array brut). - **Cloisonnement par site courant** appliqué dans le provider : un provider custom court-circuite le `SiteScopedQueryExtension`, on réplique donc sa logique (bypass_scope / site null = no-op). - `?search=` (number, nom client/fournisseur, other_label, immatriculation) ; tri `?order[displayDate]` (COALESCE full/empty), défaut `number DESC`. - Anti-N+1 : fetch-join `client`/`supplier`/`site` (ManyToOne). - Item : 404 hors périmètre site ou soft-delete. ### Vérifications - `make test` **vert (811 tests)**, suite Architecture verte (`CollectionsArePaginatedTest` OK). - `make php-cs-fixer-allow-risky` propre. - Smoke logique normalizer (masque `AB-123-CD`, rejet 422, free format) + format numéro `86-TP-0002` validés. > Tests fonctionnels dédiés (numérotation/concurrence, contrat JSON, RG complètes) : **ERP-187**.
matthieu added the backM5-Ticket-peseetype/feat labels 2026-06-18 09:02:02 +00:00
matthieu changed target branch from feat/erp-184-pesee-pont-bascule to develop 2026-06-18 12:47:08 +00:00
matthieu added 1 commit 2026-06-18 12:47:08 +00:00
Logique métier d'écriture et de lecture du ticket de pesée (M5).

Processor (POST/PATCH) :
- résolution du site courant (CurrentSiteProvider) + attribution du numéro
  {siteCode}-TP-{NNNN} à la création, immuables ensuite (RG-5.02 / RG-5.09) ;
- exclusivité de la contrepartie CLIENT/FOURNISSEUR/AUTRE — null-ification des
  champs hors-branche (RG-5.03, garde-fou CHECK Postgres) ;
- normalisation immatriculation trim/UPPER + masque XX-000-XX hors « Tout
  format », 422 inline sur le champ si invalide (RG-5.01 / RG-5.10) ;
- DSD autoritaire pour les pesées AUTO via DsdAllocator (verrou), MANUEL conservé
  (RG-5.04) ;
- poids net = plein − vide recalculé (RG-5.05).

Provider (GET) : liste paginée (Paginator ORM, règle n°13), recherche ?search=,
tri ?order[displayDate], cloisonnement par site courant appliqué dans le provider
(le SiteScopedQueryExtension ne traverse pas un provider custom), fetch-join
client/supplier/site anti-N+1, 404 hors périmètre / soft-delete.

Ajouts : WeighingTicketNumberAllocator (compteur weighing_ticket_counter,
SELECT FOR UPDATE), WeighingTicketFieldNormalizer, InvalidImmatriculationException
+ alias DI.

make test vert (811), Architecture vert (CollectionsArePaginatedTest).
matthieu merged commit c0dadd79ff into develop 2026-06-18 12:47:12 +00:00
matthieu deleted branch feat/erp-185-provider-processor-weighingticket 2026-06-18 12:47:12 +00:00
Sign in to join this conversation.