feat(technique) : validations RG comptables server-side (RG-3.07 Virement/banque, RG-3.08 LCR/RIB) (ERP-136)
- Provider::validatePaymentTypeConsistency (Assert\Callback, miroir Supplier ERP-89) : RG-3.07 VIREMENT impose une banque (violation sur bank), RG-3.08 LCR impose au moins un RIB (violation sur paymentType). - ProviderProcessor : docblock realigne (RG-3.07/3.08 portees par l'entite). - AbstractProviderApiTestCase::bank() helper referentiel. - ProviderAccountingValidationTest : 4 cas (negatif 422 / positif 200) par RG. Les RG-3.03/3.05/3.09 (contraintes d'entite) et l'ecriture cloisonnee (gardes processors, RG-3.17/2.13) etaient deja posees en ERP-133/134/135 et restent couvertes.
This commit is contained in:
@@ -140,6 +140,12 @@ class Provider implements TimestampableInterface, BlamableInterface
|
|||||||
*/
|
*/
|
||||||
private const string REQUIRED_CATEGORY_TYPE_CODE = 'PRESTATAIRE';
|
private const string REQUIRED_CATEGORY_TYPE_CODE = 'PRESTATAIRE';
|
||||||
|
|
||||||
|
/** Code pivot du type de reglement imposant une banque (RG-3.07). */
|
||||||
|
private const string PAYMENT_TYPE_VIREMENT = 'VIREMENT';
|
||||||
|
|
||||||
|
/** Code pivot du type de reglement imposant au moins un RIB (RG-3.08). */
|
||||||
|
private const string PAYMENT_TYPE_LCR = 'LCR';
|
||||||
|
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
@@ -291,6 +297,44 @@ class Provider implements TimestampableInterface, BlamableInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RG-3.07 / RG-3.08 : coherence du type de reglement comptable. Comme au M2
|
||||||
|
* (decision figee ERP-89, jumeau Supplier::validatePaymentTypeConsistency),
|
||||||
|
* ces RG inter-champs passent par une contrainte d'entite (Assert\Callback +
|
||||||
|
* ->atPath()) et NON par le ProviderProcessor, afin que chaque 422 porte un
|
||||||
|
* propertyPath exploitable par extractApiViolations (mapping inline sous le
|
||||||
|
* champ, pas un toast — convention ERP-101).
|
||||||
|
* - RG-3.07 : paymentType = VIREMENT impose une banque -> violation sur `bank`.
|
||||||
|
* - RG-3.08 : paymentType = LCR impose au moins un RIB -> violation sur
|
||||||
|
* `paymentType` (les RIB n'ont pas de champ de formulaire ou s'ancrer quand
|
||||||
|
* la liste est vide ; l'erreur s'affiche donc sous le select « Type de
|
||||||
|
* règlement », binde cote front). Le 409 sur DELETE du dernier RIB en LCR est
|
||||||
|
* porte par le ProviderRibProcessor (ERP-135).
|
||||||
|
*
|
||||||
|
* Ces champs vivant dans le groupe d'ecriture comptable (absent du POST, qui
|
||||||
|
* n'expose que provider:write:main), la contrainte ne mord en pratique que sur
|
||||||
|
* le PATCH de l'onglet Comptabilite.
|
||||||
|
*/
|
||||||
|
#[Assert\Callback]
|
||||||
|
public function validatePaymentTypeConsistency(ExecutionContextInterface $context): void
|
||||||
|
{
|
||||||
|
$paymentCode = $this->paymentType?->getCode();
|
||||||
|
|
||||||
|
if (self::PAYMENT_TYPE_VIREMENT === $paymentCode && null === $this->bank) {
|
||||||
|
$context->buildViolation('La banque est obligatoire pour le type de règlement Virement.')
|
||||||
|
->atPath('bank')
|
||||||
|
->addViolation()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::PAYMENT_TYPE_LCR === $paymentCode && $this->ribs->isEmpty()) {
|
||||||
|
$context->buildViolation('Au moins un RIB est obligatoire pour le type de règlement LCR.')
|
||||||
|
->atPath('paymentType')
|
||||||
|
->addViolation()
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
|||||||
+7
-6
@@ -52,12 +52,13 @@ use Symfony\Component\Validator\ConstraintViolationList;
|
|||||||
* collisions d'unicite en 409 (RG-3.10 doublon de nom ; RG-3.14 conflit de
|
* collisions d'unicite en 409 (RG-3.10 doublon de nom ; RG-3.14 conflit de
|
||||||
* restauration).
|
* restauration).
|
||||||
*
|
*
|
||||||
* La RG-3.09 (categorie de type PRESTATAIRE) est portee par un Assert\Callback +
|
* Les RG inter-champs RG-3.07 (Virement -> banque), RG-3.08 (LCR -> >= 1 RIB) et
|
||||||
* ->atPath() sur l'entite Provider (joue par API Platform AVANT ce processor),
|
* RG-3.09 (categorie de type PRESTATAIRE) sont portees par des Assert\Callback +
|
||||||
* pour que la 422 porte un propertyPath consommable par extractApiViolations
|
* ->atPath() sur les entites Provider / ProviderAddress (jouees par API Platform
|
||||||
* (mapping inline, pas un toast — convention ERP-101). Les RG-3.07 (Virement ->
|
* AVANT ce processor), pour que chaque 422 porte un propertyPath consommable par
|
||||||
* banque) et RG-3.08 (LCR -> RIB) relevent de l'onglet Comptabilite / sous-ressource
|
* extractApiViolations (mapping inline, pas un toast — convention ERP-101). Le 409
|
||||||
* RIB (ticket dedie) et ne sont pas portees ici.
|
* sur DELETE du dernier RIB en LCR (volet ecriture de RG-3.08) est porte par le
|
||||||
|
* ProviderRibProcessor (ERP-135).
|
||||||
*
|
*
|
||||||
* @implements ProcessorInterface<Provider, Provider>
|
* @implements ProcessorInterface<Provider, Provider>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Tests\Module\Technique\Api;
|
|||||||
use ApiPlatform\Symfony\Bundle\Test\Client;
|
use ApiPlatform\Symfony\Bundle\Test\Client;
|
||||||
use App\Module\Catalog\Domain\Entity\Category;
|
use App\Module\Catalog\Domain\Entity\Category;
|
||||||
use App\Module\Catalog\Domain\Entity\CategoryType;
|
use App\Module\Catalog\Domain\Entity\CategoryType;
|
||||||
|
use App\Module\Commercial\Domain\Entity\Bank;
|
||||||
use App\Module\Commercial\Domain\Entity\PaymentType;
|
use App\Module\Commercial\Domain\Entity\PaymentType;
|
||||||
use App\Module\Core\Domain\Entity\Permission;
|
use App\Module\Core\Domain\Entity\Permission;
|
||||||
use App\Module\Core\Domain\Entity\Role;
|
use App\Module\Core\Domain\Entity\Role;
|
||||||
@@ -335,6 +336,22 @@ abstract class AbstractProviderApiTestCase extends AbstractApiTestCase
|
|||||||
return $paymentType;
|
return $paymentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recupere une banque seedee (CommercialReferentialFixtures) par code (ex. SG).
|
||||||
|
* Echoue explicitement si absente (fixtures non chargees).
|
||||||
|
*/
|
||||||
|
protected function bank(string $code): Bank
|
||||||
|
{
|
||||||
|
$bank = $this->getEm()->getRepository(Bank::class)->findOneBy(['code' => $code]);
|
||||||
|
|
||||||
|
self::assertNotNull(
|
||||||
|
$bank,
|
||||||
|
sprintf('Banque "%s" introuvable : fixtures comptables chargees (make test-db-setup) ?', $code),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $bank;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indexe les violations d'un corps 422 par propertyPath (assert ciblee).
|
* Indexe les violations d'un corps 422 par propertyPath (assert ciblee).
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Tests\Module\Technique\Api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests fonctionnels des RG comptables inter-champs portees par les Assert\Callback
|
||||||
|
* de l'entite Provider (M3, RG-3.07 / RG-3.08), via le PATCH de l'onglet
|
||||||
|
* Comptabilite (groupe provider:write:accounting). On asserte le code HTTP et le
|
||||||
|
* propertyPath de la violation (consommable par extractApiViolations cote front,
|
||||||
|
* ERP-101). Jumeau de SupplierAccountingApiTest (M2), sans le bloc « completude de
|
||||||
|
* l'onglet » : le prestataire est minimal et n'impose pas les six scalaires
|
||||||
|
* comptables (spec M3 § 3.1).
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class ProviderAccountingValidationTest extends AbstractProviderApiTestCase
|
||||||
|
{
|
||||||
|
// === RG-3.07 : Virement impose une banque ===
|
||||||
|
|
||||||
|
public function testVirementWithoutBankReturns422OnBankPath(): void
|
||||||
|
{
|
||||||
|
$client = $this->createAdminClient();
|
||||||
|
$seed = $this->seedProvider('Virement No Bank');
|
||||||
|
|
||||||
|
$response = $client->request('PATCH', '/api/providers/'.$seed->getId(), [
|
||||||
|
'headers' => ['Content-Type' => self::MERGE, 'Accept' => self::LD],
|
||||||
|
'json' => ['paymentType' => '/api/payment_types/'.$this->paymentType('VIREMENT')->getId()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertResponseStatusCodeSame(422);
|
||||||
|
self::assertArrayHasKey('bank', $this->violationsByPath($response->toArray(false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVirementWithBankReturns200(): void
|
||||||
|
{
|
||||||
|
$client = $this->createAdminClient();
|
||||||
|
$seed = $this->seedProvider('Virement With Bank');
|
||||||
|
|
||||||
|
$client->request('PATCH', '/api/providers/'.$seed->getId(), [
|
||||||
|
'headers' => ['Content-Type' => self::MERGE],
|
||||||
|
'json' => [
|
||||||
|
'paymentType' => '/api/payment_types/'.$this->paymentType('VIREMENT')->getId(),
|
||||||
|
'bank' => '/api/banks/'.$this->bank('SG')->getId(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertResponseStatusCodeSame(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === RG-3.08 : LCR impose au moins un RIB (volet ecriture du formulaire) ===
|
||||||
|
|
||||||
|
public function testLcrWithoutRibReturns422OnPaymentTypePath(): void
|
||||||
|
{
|
||||||
|
$client = $this->createAdminClient();
|
||||||
|
$seed = $this->seedProvider('Lcr No Rib');
|
||||||
|
|
||||||
|
$response = $client->request('PATCH', '/api/providers/'.$seed->getId(), [
|
||||||
|
'headers' => ['Content-Type' => self::MERGE, 'Accept' => self::LD],
|
||||||
|
'json' => ['paymentType' => '/api/payment_types/'.$this->paymentType('LCR')->getId()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertResponseStatusCodeSame(422);
|
||||||
|
// Miroir client : violation portee sur `paymentType` (select « Type de
|
||||||
|
// règlement »), les RIB n'ayant pas de champ de formulaire pour l'ancrer.
|
||||||
|
self::assertArrayHasKey('paymentType', $this->violationsByPath($response->toArray(false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLcrWithRibReturns200(): void
|
||||||
|
{
|
||||||
|
$client = $this->createAdminClient();
|
||||||
|
$seed = $this->seedProvider('Lcr With Rib');
|
||||||
|
$this->addRib($seed);
|
||||||
|
|
||||||
|
$client->request('PATCH', '/api/providers/'.$seed->getId(), [
|
||||||
|
'headers' => ['Content-Type' => self::MERGE],
|
||||||
|
'json' => ['paymentType' => '/api/payment_types/'.$this->paymentType('LCR')->getId()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertResponseStatusCodeSame(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// violationsByPath() : helper mutualise dans AbstractProviderApiTestCase.
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user