feat(commercial) : validators M2 fournisseurs (RG-2.03/2.07/2.08/2.10) (ERP-89) (#68)
Auto Tag Develop / tag (push) Successful in 7s
Auto Tag Develop / tag (push) Successful in 7s
Étape 4/7 du M2 fournisseurs — stackée sur #67 (ERP-88). ## Périmètre (RG-2.03 / 2.07 / 2.08 / 2.10) Décision figée ERP-89 : les RG inter-champs passent par `Assert\Callback` + `->atPath()` sur l'entité Supplier (et non dans le Processor), pour que chaque 422 porte un `propertyPath` consommable par `extractApiViolations` (mapping inline, pas un toast — ERP-101). - **RG-2.10** — `Supplier::validateCategoryType()` → `atPath('categories')` : catégories de type FOURNISSEUR uniquement sur `supplier.categories` (miroir d'ERP-88 côté adresse). - **RG-2.07** — `Supplier::validatePaymentTypeConsistency()` → `atPath('bank')` : VIREMENT impose une banque. - **RG-2.08** — même Callback → `atPath('ribs')` : LCR impose ≥ 1 RIB (le 409 sur DELETE du dernier RIB en LCR reste porté par ERP-88). - **RG-2.03** — `SupplierInformationCompletenessValidator` (8 champs Information dont `volumeForecast`), invoqué par le `SupplierProcessor` après détection back du rôle Commerciale via `BusinessRoleAwareInterface`. Le Processor ne porte que rôle / mode strict / gating. ## Tests (16, verts) - `SupplierValidationTest` — Callbacks RG-2.07/2.08/2.10, assertion par propertyPath. - `SupplierInformationCompletenessValidatorTest` — complétude / champs manquants / zéros valides. - `SupplierProcessorTest` — détection rôle RG-2.03 (POST + PATCH main-only + non-Commerciale). `make test` : 499 tests OK. `php-cs-fixer` : clean. --------- Co-authored-by: admin malio <malio@yuno.malio.fr> Co-authored-by: Matthieu <contact@malio.fr> Reviewed-on: #68 Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr> Co-committed-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
This commit was merged in pull request #68.
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Module\Commercial\Domain\Entity;
|
||||
|
||||
use App\Module\Commercial\Domain\Entity\Bank;
|
||||
use App\Module\Commercial\Domain\Entity\PaymentType;
|
||||
use App\Module\Commercial\Domain\Entity\Supplier;
|
||||
use App\Module\Commercial\Domain\Entity\SupplierRib;
|
||||
use App\Shared\Domain\Contract\CategoryInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||
|
||||
/**
|
||||
* Tests des contraintes inter-champs de l'entite Supplier portees par
|
||||
* Assert\Callback (decision figee ERP-89) : RG-2.10 (categorie de type
|
||||
* FOURNISSEUR), RG-2.07 (Virement -> banque), RG-2.08 (LCR -> >= 1 RIB).
|
||||
*
|
||||
* On valide l'entite avec le validator Symfony (mapping par attributs) et on
|
||||
* assert le propertyPath exact de chaque violation (contrat ERP-101 :
|
||||
* exploitable par extractApiViolations). Pas de base : les Callback ne touchent
|
||||
* que des champs en memoire (categories via un double CategoryInterface).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SupplierValidationTest extends TestCase
|
||||
{
|
||||
private ValidatorInterface $validator;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->validator = Validation::createValidatorBuilder()
|
||||
->enableAttributeMapping()
|
||||
->getValidator()
|
||||
;
|
||||
}
|
||||
|
||||
// === RG-2.10 : categories de type FOURNISSEUR ===
|
||||
|
||||
public function testFournisseurCategoryIsAccepted(): void
|
||||
{
|
||||
$supplier = $this->validSupplier();
|
||||
|
||||
self::assertSame([], $this->violationPaths($supplier));
|
||||
}
|
||||
|
||||
public function testNonFournisseurCategoryIsRejectedOnCategoriesPath(): void
|
||||
{
|
||||
$supplier = new Supplier();
|
||||
$supplier->setCompanyName('Recycla SAS');
|
||||
$supplier->addCategory($this->category('CLIENT'));
|
||||
|
||||
self::assertContains('categories', $this->violationPaths($supplier));
|
||||
}
|
||||
|
||||
// === RG-2.07 : Virement impose une banque ===
|
||||
|
||||
public function testVirementWithoutBankIsRejectedOnBankPath(): void
|
||||
{
|
||||
$supplier = $this->validSupplier();
|
||||
$supplier->setPaymentType($this->paymentType('VIREMENT'));
|
||||
|
||||
self::assertContains('bank', $this->violationPaths($supplier));
|
||||
}
|
||||
|
||||
public function testVirementWithBankPasses(): void
|
||||
{
|
||||
$supplier = $this->validSupplier();
|
||||
$supplier->setPaymentType($this->paymentType('VIREMENT'));
|
||||
$supplier->setBank(new Bank());
|
||||
|
||||
self::assertNotContains('bank', $this->violationPaths($supplier));
|
||||
}
|
||||
|
||||
// === RG-2.08 : LCR impose au moins un RIB ===
|
||||
|
||||
public function testLcrWithoutRibIsRejectedOnRibsPath(): void
|
||||
{
|
||||
$supplier = $this->validSupplier();
|
||||
$supplier->setPaymentType($this->paymentType('LCR'));
|
||||
|
||||
self::assertContains('ribs', $this->violationPaths($supplier));
|
||||
}
|
||||
|
||||
public function testLcrWithRibPasses(): void
|
||||
{
|
||||
$supplier = $this->validSupplier();
|
||||
$supplier->setPaymentType($this->paymentType('LCR'));
|
||||
$supplier->addRib(new SupplierRib());
|
||||
|
||||
self::assertNotContains('ribs', $this->violationPaths($supplier));
|
||||
}
|
||||
|
||||
public function testNeutralPaymentTypeRequiresNeitherBankNorRib(): void
|
||||
{
|
||||
// Un type de reglement neutre (ni VIREMENT ni LCR) n'exige ni banque ni RIB.
|
||||
$supplier = $this->validSupplier();
|
||||
$supplier->setPaymentType($this->paymentType('CHEQUE'));
|
||||
|
||||
$paths = $this->violationPaths($supplier);
|
||||
self::assertNotContains('bank', $paths);
|
||||
self::assertNotContains('ribs', $paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fournisseur valide (nom + 1 categorie FOURNISSEUR), sans onglet
|
||||
* Comptabilite renseigne : sert de base aux tests RG-2.07/2.08.
|
||||
*/
|
||||
private function validSupplier(): Supplier
|
||||
{
|
||||
$supplier = new Supplier();
|
||||
$supplier->setCompanyName('Recycla SAS');
|
||||
$supplier->addCategory($this->category('FOURNISSEUR'));
|
||||
|
||||
return $supplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string> propertyPaths des violations levees par le validator
|
||||
*/
|
||||
private function violationPaths(Supplier $supplier): array
|
||||
{
|
||||
$paths = [];
|
||||
foreach ($this->validator->validate($supplier) as $violation) {
|
||||
$paths[] = $violation->getPropertyPath();
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Double minimal de CategoryInterface (pas d'acces base) renvoyant le code de
|
||||
* type de categorie voulu — seul element regarde par validateCategoryType.
|
||||
*/
|
||||
private function category(string $typeCode): CategoryInterface
|
||||
{
|
||||
return new class($typeCode) implements CategoryInterface {
|
||||
public function __construct(private readonly string $typeCode) {}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return 'Categorie test';
|
||||
}
|
||||
|
||||
public function getCode(): ?string
|
||||
{
|
||||
return 'TEST';
|
||||
}
|
||||
|
||||
public function getCategoryTypeCode(): ?string
|
||||
{
|
||||
return $this->typeCode;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private function paymentType(string $code): PaymentType
|
||||
{
|
||||
$type = new PaymentType();
|
||||
$type->setCode($code);
|
||||
$type->setLabel($code);
|
||||
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user