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,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Module\Logistique\Api;
|
||||
|
||||
/**
|
||||
* Cycle de vie brouillon -> valide du ticket de pesee (ERP-193, spec-back § 2.14).
|
||||
*
|
||||
* Couvre :
|
||||
* - une pesee peut etre enregistree SANS contrepartie ni immatriculation : le POST
|
||||
* cree un BROUILLON (status DRAFT, pas de numero) ;
|
||||
* - la validation (PATCH /validate) exige les 3 champs du haut (type + champ
|
||||
* contrepartie + immatriculation) ET les 2 pesees (groupe `finalize`) ;
|
||||
* - une validation complete attribue le numero {siteCode}-TP-{NNNN} et passe le
|
||||
* ticket en VALIDATED.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class WeighingTicketLifecycleTest extends AbstractWeighingTicketApiTestCase
|
||||
{
|
||||
public function testWeighingOnlyCreatesDraftWithoutNumber(): void
|
||||
{
|
||||
$http = $this->authManageOnSite($this->siteByCode('86'));
|
||||
|
||||
// Pesee a vide seule : ni contrepartie, ni immatriculation.
|
||||
$body = $this->postTicket($http, [
|
||||
'emptyDate' => '2026-06-17T09:00:00+02:00',
|
||||
'emptyWeight' => 7150,
|
||||
'emptyMode' => 'AUTO',
|
||||
])->toArray();
|
||||
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
self::assertSame('DRAFT', $body['status']);
|
||||
self::assertArrayNotHasKey('number', $body, 'Un brouillon n\'a pas encore de numero (skip_null_values).');
|
||||
self::assertSame(7150, $body['emptyWeight']);
|
||||
}
|
||||
|
||||
public function testDraftWithIncompleteCounterpartyIsPersistedWithoutBranch(): void
|
||||
{
|
||||
$http = $this->authManageOnSite($this->siteByCode('86'));
|
||||
|
||||
// Brouillon « contrepartie incomplete » : type CLIENT choisi mais client pas
|
||||
// encore selectionne (cas reel : l'operateur ouvre le menu puis pese). Le
|
||||
// Callback de coherence ne joue qu'a la validation (groupe finalize) ->
|
||||
// SANS normalisation cote Processor, le persist violerait chk_wt_client_branch
|
||||
// (counterparty_type='CLIENT' + client_id NULL) et leverait une 500.
|
||||
$body = $this->postTicket($http, [
|
||||
'counterpartyType' => 'CLIENT',
|
||||
'emptyDate' => '2026-06-17T09:00:00+02:00',
|
||||
'emptyWeight' => 7150,
|
||||
'emptyMode' => 'AUTO',
|
||||
])->toArray();
|
||||
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
self::assertSame('DRAFT', $body['status']);
|
||||
// La contrepartie incoherente est retiree (pas persistee a moitie) : le
|
||||
// brouillon reste enregistrable, la coherence est exigee a la validation.
|
||||
self::assertNull($body['counterpartyType'] ?? null);
|
||||
self::assertSame(7150, $body['emptyWeight']);
|
||||
}
|
||||
|
||||
public function testDraftWithEmptyOtherLabelIsPersistedWithoutBranch(): void
|
||||
{
|
||||
$http = $this->authManageOnSite($this->siteByCode('86'));
|
||||
|
||||
// Meme piege en branche AUTRE : type AUTRE mais libelle vide -> le normalizer
|
||||
// ramene otherLabel a NULL, ce qui violait chk_wt_other_branch (500).
|
||||
$body = $this->postTicket($http, [
|
||||
'counterpartyType' => 'AUTRE',
|
||||
'otherLabel' => ' ',
|
||||
'emptyDate' => '2026-06-17T09:00:00+02:00',
|
||||
'emptyWeight' => 7150,
|
||||
'emptyMode' => 'AUTO',
|
||||
])->toArray();
|
||||
|
||||
self::assertResponseStatusCodeSame(201);
|
||||
self::assertSame('DRAFT', $body['status']);
|
||||
self::assertNull($body['counterpartyType'] ?? null);
|
||||
}
|
||||
|
||||
public function testValidateRequiresCounterparty(): void
|
||||
{
|
||||
$http = $this->authManageOnSite($this->siteByCode('86'));
|
||||
|
||||
// Brouillon complet cote pesees + immatriculation, mais SANS contrepartie.
|
||||
$id = (int) $this->postTicket($http, [
|
||||
'immatriculation' => 'AB-123-CD',
|
||||
'emptyDate' => '2026-06-17T09:00:00+02:00',
|
||||
'emptyWeight' => 7150,
|
||||
'emptyMode' => 'AUTO',
|
||||
'fullDate' => '2026-06-17T09:12:00+02:00',
|
||||
'fullWeight' => 14300,
|
||||
'fullMode' => 'AUTO',
|
||||
])->toArray()['id'];
|
||||
|
||||
$response = $this->validateTicket($http, $id);
|
||||
|
||||
self::assertResponseStatusCodeSame(422);
|
||||
self::assertViolationOnPath($response, 'counterpartyType');
|
||||
}
|
||||
|
||||
public function testValidateRequiresBothWeighings(): void
|
||||
{
|
||||
$http = $this->authManageOnSite($this->siteByCode('86'));
|
||||
$client = $this->seedTestClient('Lifecycle');
|
||||
|
||||
// Brouillon avec contrepartie + immat + UNE seule pesee (a vide).
|
||||
$id = (int) $this->postTicket($http, [
|
||||
'counterpartyType' => 'CLIENT',
|
||||
'client' => $this->clientIri($client),
|
||||
'immatriculation' => 'AB-123-CD',
|
||||
'emptyDate' => '2026-06-17T09:00:00+02:00',
|
||||
'emptyWeight' => 7150,
|
||||
'emptyMode' => 'AUTO',
|
||||
])->toArray()['id'];
|
||||
|
||||
$response = $this->validateTicket($http, $id);
|
||||
|
||||
self::assertResponseStatusCodeSame(422);
|
||||
self::assertViolationOnPath($response, 'fullWeight');
|
||||
}
|
||||
|
||||
public function testValidateAssignsNumberAndStatus(): void
|
||||
{
|
||||
$http = $this->authManageOnSite($this->siteByCode('86'));
|
||||
$client = $this->seedTestClient('LifecycleOk');
|
||||
|
||||
$validated = $this->createValidatedTicket($http, $this->validClientTicketPayload($client));
|
||||
|
||||
self::assertSame('VALIDATED', $validated['status']);
|
||||
self::assertMatchesRegularExpression('/^86-TP-\d{4}$/', (string) $validated['number']);
|
||||
self::assertSame(7150, $validated['netWeight']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user