391f383c4b
Un brouillon dont le type de contrepartie est choisi sans son champ associé (client/fournisseur null, ou libellé « Autre » vide) violait chk_wt_*_branch et levait une 500 : le callback de cohérence RG-5.03 ne joue qu'au groupe finalize, laissant passer l'incohérence à l'enregistrement du brouillon. - back : WeighingTicketProcessor retire la contrepartie entière quand le champ de branche est absent (clearCounterparty) au lieu de persister un état incohérent. N'affecte que le brouillon (à la validation, le callback finalize lève déjà une 422 avant le processor). - front : buildDraftPayload n'émet le type que si son champ associé est rempli ; la validation continue d'envoyer toujours le type pour la 422 métier. - tests : 2 cas back (CLIENT sans client, AUTRE libellé vide) + 2 cas front.
136 lines
5.4 KiB
PHP
136 lines
5.4 KiB
PHP
<?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']);
|
|
}
|
|
}
|