Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a6ec71981 |
@@ -140,6 +140,12 @@ class Provider implements TimestampableInterface, BlamableInterface
|
||||
*/
|
||||
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\GeneratedValue]
|
||||
#[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
|
||||
{
|
||||
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
|
||||
* restauration).
|
||||
*
|
||||
* La RG-3.09 (categorie de type PRESTATAIRE) est portee par un Assert\Callback +
|
||||
* ->atPath() sur l'entite Provider (joue par API Platform AVANT ce processor),
|
||||
* pour que la 422 porte un propertyPath consommable par extractApiViolations
|
||||
* (mapping inline, pas un toast — convention ERP-101). Les RG-3.07 (Virement ->
|
||||
* banque) et RG-3.08 (LCR -> RIB) relevent de l'onglet Comptabilite / sous-ressource
|
||||
* RIB (ticket dedie) et ne sont pas portees ici.
|
||||
* Les RG inter-champs RG-3.07 (Virement -> banque), RG-3.08 (LCR -> >= 1 RIB) et
|
||||
* RG-3.09 (categorie de type PRESTATAIRE) sont portees par des Assert\Callback +
|
||||
* ->atPath() sur les entites Provider / ProviderAddress (jouees par API Platform
|
||||
* AVANT ce processor), pour que chaque 422 porte un propertyPath consommable par
|
||||
* extractApiViolations (mapping inline, pas un toast — convention ERP-101). Le 409
|
||||
* sur DELETE du dernier RIB en LCR (volet ecriture de RG-3.08) est porte par le
|
||||
* ProviderRibProcessor (ERP-135).
|
||||
*
|
||||
* @implements ProcessorInterface<Provider, Provider>
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace App\Tests\Module\Technique\Api;
|
||||
use ApiPlatform\Symfony\Bundle\Test\Client;
|
||||
use App\Module\Catalog\Domain\Entity\Category;
|
||||
use App\Module\Catalog\Domain\Entity\CategoryType;
|
||||
use App\Module\Commercial\Domain\Entity\Bank;
|
||||
use App\Module\Commercial\Domain\Entity\PaymentType;
|
||||
use App\Module\Core\Domain\Entity\Permission;
|
||||
use App\Module\Core\Domain\Entity\Role;
|
||||
@@ -335,6 +336,22 @@ abstract class AbstractProviderApiTestCase extends AbstractApiTestCase
|
||||
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).
|
||||
*
|
||||
|
||||
@@ -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